[CI] Add more checks (#43)
This commit is contained in:
parent
3814b8a504
commit
018dd8164c
73 changed files with 16332 additions and 6 deletions
|
@ -28,6 +28,7 @@ pipeline:
|
||||||
# - make got-swag # Commented out until we figured out how to get this working on drone
|
# - make got-swag # Commented out until we figured out how to get this working on drone
|
||||||
- make ineffassign-check
|
- make ineffassign-check
|
||||||
- make misspell-check
|
- make misspell-check
|
||||||
|
- make goconst-check
|
||||||
- make build
|
- make build
|
||||||
when:
|
when:
|
||||||
event: [ push, tag, pull_request ]
|
event: [ push, tag, pull_request ]
|
||||||
|
|
|
@ -124,3 +124,11 @@ Sorry for some of them being in German, I'll tranlate them at some point.
|
||||||
* [ ] Mgl., dass die Instanz geschlossen ist, also sich keiner registrieren kann, und man sich einloggen muss
|
* [ ] Mgl., dass die Instanz geschlossen ist, also sich keiner registrieren kann, und man sich einloggen muss
|
||||||
* [ ] mgl. zum Emailmaskieren haben (in den Nutzereinstellungen, wenn man seine Email nicht an alle Welt rausposaunen will)
|
* [ ] mgl. zum Emailmaskieren haben (in den Nutzereinstellungen, wenn man seine Email nicht an alle Welt rausposaunen will)
|
||||||
* [ ] Mgl. zum Accountlöschen haben (so richtig krass mit emailverifiezierung und dass alle Privaten Listen gelöscht werden und man alle geteilten entweder wem übertragen muss oder auf privat stellen)
|
* [ ] Mgl. zum Accountlöschen haben (so richtig krass mit emailverifiezierung und dass alle Privaten Listen gelöscht werden und man alle geteilten entweder wem übertragen muss oder auf privat stellen)
|
||||||
|
|
||||||
|
### Linters
|
||||||
|
|
||||||
|
* [x] goconst
|
||||||
|
* [ ] Gosimple -> waiting for mod
|
||||||
|
* [ ] Staticcheck -> waiting for mod
|
||||||
|
* [ ] unused -> waiting for mod
|
||||||
|
* [ ] gosec -> waiting for mod
|
36
Makefile
36
Makefile
|
@ -187,3 +187,39 @@ gocyclo-check:
|
||||||
go install $(GOFLAGS) github.com/fzipp/gocyclo; \
|
go install $(GOFLAGS) github.com/fzipp/gocyclo; \
|
||||||
fi
|
fi
|
||||||
for S in $(GOFILES); do gocyclo -over 14 $$S || exit 1; done;
|
for S in $(GOFILES); do gocyclo -over 14 $$S || exit 1; done;
|
||||||
|
|
||||||
|
.PHONY: gosimple-check
|
||||||
|
gosimple-check:
|
||||||
|
@hash gosimple > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
go get honnef.co/go/tools/cmd/gosimple; \
|
||||||
|
fi
|
||||||
|
for S in $(PACKAGES); do gosimple $$S || exit 1; done;
|
||||||
|
|
||||||
|
.PHONY: static-check
|
||||||
|
static-check:
|
||||||
|
@hash gocyclo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
go get honnef.co/go/tools/cmd/staticcheck; \
|
||||||
|
fi
|
||||||
|
staticcheck;
|
||||||
|
|
||||||
|
.PHONY: unused-check
|
||||||
|
unused-check:
|
||||||
|
@hash unused > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
go get honnef.co/go/tools/cmd/unused; \
|
||||||
|
fi
|
||||||
|
unused;
|
||||||
|
|
||||||
|
.PHONY: gosec-check
|
||||||
|
gosec-check:
|
||||||
|
@hash ./bin/gosec > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s 1.2.0; \
|
||||||
|
fi
|
||||||
|
for S in $(PACKAGES); do ./bin/gosec $$S || exit 1; done;
|
||||||
|
|
||||||
|
.PHONY: goconst-check
|
||||||
|
goconst-check:
|
||||||
|
@hash goconst > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
go get github.com/jgautheron/goconst/cmd/goconst; \
|
||||||
|
fi
|
||||||
|
for S in $(PACKAGES); do goconst $$S || exit 1; done;
|
||||||
|
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -23,7 +23,6 @@ require (
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf
|
||||||
github.com/client9/misspell v0.3.4
|
github.com/client9/misspell v0.3.4
|
||||||
github.com/cweill/gotests v1.5.2 // indirect
|
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
|
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
|
||||||
github.com/garyburd/redigo v1.6.0 // indirect
|
github.com/garyburd/redigo v1.6.0 // indirect
|
||||||
|
@ -39,7 +38,9 @@ require (
|
||||||
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2
|
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2
|
||||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc
|
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc
|
||||||
github.com/imdario/mergo v0.3.6
|
github.com/imdario/mergo v0.3.6
|
||||||
|
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb
|
||||||
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970
|
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970
|
||||||
|
github.com/kisielk/gotool v1.0.0 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/labstack/echo v3.3.5+incompatible
|
github.com/labstack/echo v3.3.5+incompatible
|
||||||
github.com/labstack/gommon v0.2.8
|
github.com/labstack/gommon v0.2.8
|
||||||
|
@ -68,4 +69,5 @@ require (
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
gopkg.in/testfixtures.v2 v2.5.3
|
gopkg.in/testfixtures.v2 v2.5.3
|
||||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||||
|
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3
|
||||||
)
|
)
|
||||||
|
|
11
go.sum
11
go.sum
|
@ -18,7 +18,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
||||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cweill/gotests v1.5.2 h1:kKqmKmS2wCV3tuLnfpbiuN8OlkosQZTpCfiqmiuNAsA=
|
github.com/cweill/gotests v1.5.2 h1:kKqmKmS2wCV3tuLnfpbiuN8OlkosQZTpCfiqmiuNAsA=
|
||||||
github.com/cweill/gotests v1.5.2/go.mod h1:XZYOJkGVkCRoymaIzmp9Wyi3rUgfA3oOnkuljYrjFV8=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs=
|
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs=
|
||||||
|
@ -68,10 +67,14 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
|
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb h1:D5s1HIu80AcMGcqmk7fNIVptmAubVHHaj3v5Upex6Zs=
|
||||||
|
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb/go.mod h1:82TxjOpWQiPmywlbIaB2ZkqJoSYJdLGPgAJDvM3PbKc=
|
||||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970 h1:0+1ZURVRim6FxA/jhWhJklsgoWc69q1sxlIu2Ztnhy0=
|
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970 h1:0+1ZURVRim6FxA/jhWhJklsgoWc69q1sxlIu2Ztnhy0=
|
||||||
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970/go.mod h1:iYGcTYIPUvEWhFo6aKUuLchs+AV4ssYdyuBbQJZGcBk=
|
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970/go.mod h1:iYGcTYIPUvEWhFo6aKUuLchs+AV4ssYdyuBbQJZGcBk=
|
||||||
|
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
@ -141,10 +144,6 @@ github.com/swaggo/files v0.0.0-20180215091130-49c8a91ea3fa h1:194s4modF+3X3POBfG
|
||||||
github.com/swaggo/files v0.0.0-20180215091130-49c8a91ea3fa/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
|
github.com/swaggo/files v0.0.0-20180215091130-49c8a91ea3fa/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
|
||||||
github.com/swaggo/gin-swagger v1.0.0 h1:k6Nn1jV49u+SNIWt7kejQS/iENZKZVMCNQrKOYatNF8=
|
github.com/swaggo/gin-swagger v1.0.0 h1:k6Nn1jV49u+SNIWt7kejQS/iENZKZVMCNQrKOYatNF8=
|
||||||
github.com/swaggo/gin-swagger v1.0.0/go.mod h1:Mt37wE46iUaTAOv+HSnHbJYssKGqbS25X19lNF4YpBo=
|
github.com/swaggo/gin-swagger v1.0.0/go.mod h1:Mt37wE46iUaTAOv+HSnHbJYssKGqbS25X19lNF4YpBo=
|
||||||
github.com/swaggo/swag v1.4.0 h1:exX5ES4CdJWCCKmVPE+FAIN66cnHeMHU3i2SCMibBZc=
|
|
||||||
github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
|
||||||
github.com/swaggo/swag v1.4.1-0.20181129020348-1c8533a91397 h1:xhlin3d0mSsxQlwxS+fHILT6PgG4Cmc2OZgzZL2bemI=
|
|
||||||
github.com/swaggo/swag v1.4.1-0.20181129020348-1c8533a91397/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
|
||||||
github.com/swaggo/swag v1.4.1-0.20181210033626-0e12fd5eb026 h1:XAOjF3QgjDUkVrPMO4rYvNptSHQgUlHwQsEdJOTxHQ8=
|
github.com/swaggo/swag v1.4.1-0.20181210033626-0e12fd5eb026 h1:XAOjF3QgjDUkVrPMO4rYvNptSHQgUlHwQsEdJOTxHQ8=
|
||||||
github.com/swaggo/swag v1.4.1-0.20181210033626-0e12fd5eb026/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
github.com/swaggo/swag v1.4.1-0.20181210033626-0e12fd5eb026/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
||||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||||
|
@ -202,3 +201,5 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3 h1:LyX67rVB0kBUFoROrQfzKwdrYLH1cRzHibxdJW85J1c=
|
||||||
|
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
5
tools.go
5
tools.go
|
@ -29,4 +29,9 @@ import (
|
||||||
_ "github.com/swaggo/echo-swagger"
|
_ "github.com/swaggo/echo-swagger"
|
||||||
_ "github.com/swaggo/swag/cmd/swag"
|
_ "github.com/swaggo/swag/cmd/swag"
|
||||||
_ "golang.org/x/lint/golint"
|
_ "golang.org/x/lint/golint"
|
||||||
|
|
||||||
|
_ "github.com/jgautheron/goconst/cmd/goconst"
|
||||||
|
_ "honnef.co/go/tools/cmd/gosimple"
|
||||||
|
_ "honnef.co/go/tools/cmd/staticcheck"
|
||||||
|
_ "honnef.co/go/tools/cmd/unused"
|
||||||
)
|
)
|
||||||
|
|
21
vendor/github.com/jgautheron/goconst/LICENSE
generated
vendored
Normal file
21
vendor/github.com/jgautheron/goconst/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Jonathan Gautheron
|
||||||
|
|
||||||
|
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.
|
49
vendor/github.com/jgautheron/goconst/README.md
generated
vendored
Normal file
49
vendor/github.com/jgautheron/goconst/README.md
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# goconst
|
||||||
|
|
||||||
|
Find repeated strings that could be replaced by a constant.
|
||||||
|
|
||||||
|
### Motivation
|
||||||
|
|
||||||
|
There are obvious benefits to using constants instead of repeating strings, mostly to ease maintenance. Cannot argue against changing a single constant versus many strings.
|
||||||
|
|
||||||
|
While this could be considered a beginner mistake, across time, multiple packages and large codebases, some repetition could have slipped in.
|
||||||
|
|
||||||
|
### Get Started
|
||||||
|
|
||||||
|
$ go get github.com/jgautheron/goconst/cmd/goconst
|
||||||
|
$ goconst ./...
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
goconst ARGS <directory>
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
|
||||||
|
-ignore exclude files matching the given regular expression
|
||||||
|
-ignore-tests exclude tests from the search (default: true)
|
||||||
|
-min-occurrences report from how many occurrences (default: 2)
|
||||||
|
-min-length only report strings with the minimum given length (default: 3)
|
||||||
|
-match-constant look for existing constants matching the values
|
||||||
|
-numbers search also for duplicated numbers
|
||||||
|
-min minimum value, only works with -numbers
|
||||||
|
-max maximum value, only works with -numbers
|
||||||
|
-output output formatting (text or json)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
goconst ./...
|
||||||
|
goconst -ignore "yacc|\.pb\." $GOPATH/src/github.com/cockroachdb/cockroach/...
|
||||||
|
goconst -min-occurrences 3 -output json $GOPATH/src/github.com/cockroachdb/cockroach
|
||||||
|
goconst -numbers -min 60 -max 512 .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other static analysis tools
|
||||||
|
|
||||||
|
- [gogetimports](https://github.com/jgautheron/gogetimports): Get a JSON-formatted list of imports.
|
||||||
|
- [usedexports](https://github.com/jgautheron/usedexports): Find exported variables that could be unexported.
|
||||||
|
|
||||||
|
### License
|
||||||
|
MIT
|
166
vendor/github.com/jgautheron/goconst/cmd/goconst/main.go
generated
vendored
Normal file
166
vendor/github.com/jgautheron/goconst/cmd/goconst/main.go
generated
vendored
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jgautheron/goconst"
|
||||||
|
)
|
||||||
|
|
||||||
|
const usageDoc = `goconst: find repeated strings that could be replaced by a constant
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
goconst ARGS <directory> [<directory>...]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
|
||||||
|
-ignore exclude files matching the given regular expression
|
||||||
|
-ignore-tests exclude tests from the search (default: true)
|
||||||
|
-min-occurrences report from how many occurrences (default: 2)
|
||||||
|
-min-length only report strings with the minimum given length (default: 3)
|
||||||
|
-match-constant look for existing constants matching the strings
|
||||||
|
-numbers search also for duplicated numbers
|
||||||
|
-min minimum value, only works with -numbers
|
||||||
|
-max maximum value, only works with -numbers
|
||||||
|
-output output formatting (text or json)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
goconst ./...
|
||||||
|
goconst -ignore "yacc|\.pb\." $GOPATH/src/github.com/cockroachdb/cockroach/...
|
||||||
|
goconst -min-occurrences 3 -output json $GOPATH/src/github.com/cockroachdb/cockroach
|
||||||
|
goconst -numbers -min 60 -max 512 .
|
||||||
|
`
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagIgnore = flag.String("ignore", "", "ignore files matching the given regular expression")
|
||||||
|
flagIgnoreTests = flag.Bool("ignore-tests", true, "exclude tests from the search")
|
||||||
|
flagMinOccurrences = flag.Int("min-occurrences", 2, "report from how many occurrences")
|
||||||
|
flagMinLength = flag.Int("min-length", 3, "only report strings with the minimum given length")
|
||||||
|
flagMatchConstant = flag.Bool("match-constant", false, "look for existing constants matching the strings")
|
||||||
|
flagNumbers = flag.Bool("numbers", false, "search also for duplicated numbers")
|
||||||
|
flagMin = flag.Int("min", 0, "minimum value, only works with -numbers")
|
||||||
|
flagMax = flag.Int("max", 0, "maximum value, only works with -numbers")
|
||||||
|
flagOutput = flag.String("output", "text", "output formatting")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Usage = func() {
|
||||||
|
usage(os.Stderr)
|
||||||
|
}
|
||||||
|
flag.Parse()
|
||||||
|
log.SetPrefix("goconst: ")
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
if len(args) < 1 {
|
||||||
|
usage(os.Stderr)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
for _, path := range args {
|
||||||
|
if err := run(path); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(path string) error {
|
||||||
|
gco := goconst.New(
|
||||||
|
path,
|
||||||
|
*flagIgnore,
|
||||||
|
*flagIgnoreTests,
|
||||||
|
*flagMatchConstant,
|
||||||
|
*flagNumbers,
|
||||||
|
*flagMinLength,
|
||||||
|
)
|
||||||
|
strs, consts, err := gco.ParseTree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return printOutput(strs, consts, *flagOutput, *flagMinOccurrences, *flagMin, *flagMax)
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage(out io.Writer) {
|
||||||
|
fmt.Fprintf(out, usageDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printOutput(strs goconst.Strings, consts goconst.Constants, output string, minOccurrences, min, max int) error {
|
||||||
|
for str, item := range strs {
|
||||||
|
// Filter out items whose occurrences don't match the min value
|
||||||
|
if len(item) < minOccurrences {
|
||||||
|
delete(strs, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the value is a number
|
||||||
|
if i, err := strconv.Atoi(str); err == nil {
|
||||||
|
if min != 0 && i < min {
|
||||||
|
delete(strs, str)
|
||||||
|
}
|
||||||
|
if max != 0 && i > max {
|
||||||
|
delete(strs, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch output {
|
||||||
|
case "json":
|
||||||
|
enc := json.NewEncoder(os.Stdout)
|
||||||
|
err := enc.Encode(struct {
|
||||||
|
Strings goconst.Strings `json:"strings,omitEmpty"`
|
||||||
|
Constants goconst.Constants `json:"constants,omitEmpty"`
|
||||||
|
}{
|
||||||
|
strs, consts,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "text":
|
||||||
|
for str, item := range strs {
|
||||||
|
for _, xpos := range item {
|
||||||
|
fmt.Printf(
|
||||||
|
`%s:%d:%d:%d other occurrence(s) of "%s" found in: %s`,
|
||||||
|
xpos.Filename,
|
||||||
|
xpos.Line,
|
||||||
|
xpos.Column,
|
||||||
|
len(item)-1,
|
||||||
|
str,
|
||||||
|
occurrences(item, xpos),
|
||||||
|
)
|
||||||
|
fmt.Print("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(consts) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cst, ok := consts[str]; ok {
|
||||||
|
// const should be in the same package and exported
|
||||||
|
fmt.Printf(`A matching constant has been found for "%s": %s`, str, cst.Name)
|
||||||
|
fmt.Printf("\n\t%s\n", cst.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(`Unsupported output format: %s`, output)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func occurrences(item []goconst.ExtendedPos, current goconst.ExtendedPos) string {
|
||||||
|
occurrences := []string{}
|
||||||
|
for _, xpos := range item {
|
||||||
|
if xpos == current {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
occurrences = append(occurrences, fmt.Sprintf(
|
||||||
|
"%s:%d:%d", xpos.Filename, xpos.Line, xpos.Column,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return strings.Join(occurrences, " ")
|
||||||
|
}
|
136
vendor/github.com/jgautheron/goconst/parser.go
generated
vendored
Normal file
136
vendor/github.com/jgautheron/goconst/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
// Package goconst finds repeated strings that could be replaced by a constant.
|
||||||
|
//
|
||||||
|
// There are obvious benefits to using constants instead of repeating strings,
|
||||||
|
// mostly to ease maintenance. Cannot argue against changing a single constant versus many strings.
|
||||||
|
// While this could be considered a beginner mistake, across time,
|
||||||
|
// multiple packages and large codebases, some repetition could have slipped in.
|
||||||
|
package goconst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testSuffix = "_test.go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
// Meant to be passed via New()
|
||||||
|
path, ignore string
|
||||||
|
ignoreTests, matchConstant bool
|
||||||
|
minLength int
|
||||||
|
|
||||||
|
supportedTokens []token.Token
|
||||||
|
|
||||||
|
// Internals
|
||||||
|
strs Strings
|
||||||
|
consts Constants
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new instance of the parser.
|
||||||
|
// This is your entry point if you'd like to use goconst as an API.
|
||||||
|
func New(path, ignore string, ignoreTests, matchConstant, numbers bool, minLength int) *Parser {
|
||||||
|
supportedTokens := []token.Token{token.STRING}
|
||||||
|
if numbers {
|
||||||
|
supportedTokens = append(supportedTokens, token.INT, token.FLOAT)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Parser{
|
||||||
|
path: path,
|
||||||
|
ignore: ignore,
|
||||||
|
ignoreTests: ignoreTests,
|
||||||
|
matchConstant: matchConstant,
|
||||||
|
minLength: minLength,
|
||||||
|
supportedTokens: supportedTokens,
|
||||||
|
|
||||||
|
// Initialize the maps
|
||||||
|
strs: Strings{},
|
||||||
|
consts: Constants{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTree will search the given path for occurrences that could be moved into constants.
|
||||||
|
// If "..." is appended, the search will be recursive.
|
||||||
|
func (p *Parser) ParseTree() (Strings, Constants, error) {
|
||||||
|
pathLen := len(p.path)
|
||||||
|
// Parse recursively the given path if the recursive notation is found
|
||||||
|
if pathLen >= 5 && p.path[pathLen-3:] == "..." {
|
||||||
|
filepath.Walk(p.path[:pathLen-3], func(path string, f os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
// resume walking
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.IsDir() {
|
||||||
|
p.parseDir(path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
p.parseDir(p.path)
|
||||||
|
}
|
||||||
|
return p.strs, p.consts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseDir(dir string) error {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
pkgs, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
|
||||||
|
valid, name := true, info.Name()
|
||||||
|
|
||||||
|
if p.ignoreTests {
|
||||||
|
if strings.HasSuffix(name, testSuffix) {
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.ignore) != 0 {
|
||||||
|
match, err := regexp.MatchString(p.ignore, dir+name)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if match {
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
for fn, f := range pkg.Files {
|
||||||
|
ast.Walk(&treeVisitor{
|
||||||
|
fileSet: fset,
|
||||||
|
packageName: pkg.Name,
|
||||||
|
fileName: fn,
|
||||||
|
p: p,
|
||||||
|
}, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Strings map[string][]ExtendedPos
|
||||||
|
type Constants map[string]ConstType
|
||||||
|
|
||||||
|
type ConstType struct {
|
||||||
|
token.Position
|
||||||
|
Name, packageName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtendedPos struct {
|
||||||
|
token.Position
|
||||||
|
packageName string
|
||||||
|
}
|
143
vendor/github.com/jgautheron/goconst/visitor.go
generated
vendored
Normal file
143
vendor/github.com/jgautheron/goconst/visitor.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
package goconst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// treeVisitor carries the package name and file name
|
||||||
|
// for passing it to the imports map, and the fileSet for
|
||||||
|
// retrieving the token.Position.
|
||||||
|
type treeVisitor struct {
|
||||||
|
p *Parser
|
||||||
|
fileSet *token.FileSet
|
||||||
|
packageName, fileName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit browses the AST tree for strings that could be potentially
|
||||||
|
// replaced by constants.
|
||||||
|
// A map of existing constants is built as well (-match-constant).
|
||||||
|
func (v *treeVisitor) Visit(node ast.Node) ast.Visitor {
|
||||||
|
if node == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single case with "ast.BasicLit" would be much easier
|
||||||
|
// but then we wouldn't be able to tell in which context
|
||||||
|
// the string is defined (could be a constant definition).
|
||||||
|
switch t := node.(type) {
|
||||||
|
// Scan for constants in an attempt to match strings with existing constants
|
||||||
|
case *ast.GenDecl:
|
||||||
|
if !v.p.matchConstant {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if t.Tok != token.CONST {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, spec := range t.Specs {
|
||||||
|
val := spec.(*ast.ValueSpec)
|
||||||
|
for i, str := range val.Values {
|
||||||
|
lit, ok := str.(*ast.BasicLit)
|
||||||
|
if !ok || !v.isSupported(lit.Kind) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v.addConst(val.Names[i].Name, lit.Value, val.Names[i].Pos())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// foo := "moo"
|
||||||
|
case *ast.AssignStmt:
|
||||||
|
for _, rhs := range t.Rhs {
|
||||||
|
lit, ok := rhs.(*ast.BasicLit)
|
||||||
|
if !ok || !v.isSupported(lit.Kind) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v.addString(lit.Value, rhs.(*ast.BasicLit).Pos())
|
||||||
|
}
|
||||||
|
|
||||||
|
// if foo == "moo"
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
if t.Op != token.EQL && t.Op != token.NEQ {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
var lit *ast.BasicLit
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
lit, ok = t.X.(*ast.BasicLit)
|
||||||
|
if ok && v.isSupported(lit.Kind) {
|
||||||
|
v.addString(lit.Value, lit.Pos())
|
||||||
|
}
|
||||||
|
|
||||||
|
lit, ok = t.Y.(*ast.BasicLit)
|
||||||
|
if ok && v.isSupported(lit.Kind) {
|
||||||
|
v.addString(lit.Value, lit.Pos())
|
||||||
|
}
|
||||||
|
|
||||||
|
// case "foo":
|
||||||
|
case *ast.CaseClause:
|
||||||
|
for _, item := range t.List {
|
||||||
|
lit, ok := item.(*ast.BasicLit)
|
||||||
|
if ok && v.isSupported(lit.Kind) {
|
||||||
|
v.addString(lit.Value, lit.Pos())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return "boo"
|
||||||
|
case *ast.ReturnStmt:
|
||||||
|
for _, item := range t.Results {
|
||||||
|
lit, ok := item.(*ast.BasicLit)
|
||||||
|
if ok && v.isSupported(lit.Kind) {
|
||||||
|
v.addString(lit.Value, lit.Pos())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// addString adds a string in the map along with its position in the tree.
|
||||||
|
func (v *treeVisitor) addString(str string, pos token.Pos) {
|
||||||
|
str = strings.Replace(str, `"`, "", 2)
|
||||||
|
|
||||||
|
// Ignore empty strings
|
||||||
|
if len(str) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(str) < v.p.minLength {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := v.p.strs[str]
|
||||||
|
if !ok {
|
||||||
|
v.p.strs[str] = make([]ExtendedPos, 0)
|
||||||
|
}
|
||||||
|
v.p.strs[str] = append(v.p.strs[str], ExtendedPos{
|
||||||
|
packageName: v.packageName,
|
||||||
|
Position: v.fileSet.Position(pos),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// addConst adds a const in the map along with its position in the tree.
|
||||||
|
func (v *treeVisitor) addConst(name string, val string, pos token.Pos) {
|
||||||
|
val = strings.Replace(val, `"`, "", 2)
|
||||||
|
v.p.consts[val] = ConstType{
|
||||||
|
Name: name,
|
||||||
|
packageName: v.packageName,
|
||||||
|
Position: v.fileSet.Position(pos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *treeVisitor) isSupported(tk token.Token) bool {
|
||||||
|
for _, s := range v.p.supportedTokens {
|
||||||
|
if tk == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
23
vendor/github.com/kisielk/gotool/.travis.yml
generated
vendored
Normal file
23
vendor/github.com/kisielk/gotool/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.2
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
|
- 1.5
|
||||||
|
- 1.6
|
||||||
|
- 1.7
|
||||||
|
- 1.8
|
||||||
|
- 1.9
|
||||||
|
- master
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: master
|
||||||
|
fast_finish: true
|
||||||
|
install:
|
||||||
|
- # Skip.
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d .)
|
||||||
|
- go tool vet .
|
||||||
|
- go test -v -race ./...
|
32
vendor/github.com/kisielk/gotool/LEGAL
generated
vendored
Normal file
32
vendor/github.com/kisielk/gotool/LEGAL
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
All the files in this distribution are covered under either the MIT
|
||||||
|
license (see the file LICENSE) except some files mentioned below.
|
||||||
|
|
||||||
|
match.go, match_test.go:
|
||||||
|
|
||||||
|
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.
|
20
vendor/github.com/kisielk/gotool/LICENSE
generated
vendored
Normal file
20
vendor/github.com/kisielk/gotool/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2013 Kamil Kisiel <kamil@kamilkisiel.net>
|
||||||
|
|
||||||
|
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.
|
6
vendor/github.com/kisielk/gotool/README.md
generated
vendored
Normal file
6
vendor/github.com/kisielk/gotool/README.md
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
gotool
|
||||||
|
======
|
||||||
|
[![GoDoc](https://godoc.org/github.com/kisielk/gotool?status.svg)](https://godoc.org/github.com/kisielk/gotool)
|
||||||
|
[![Build Status](https://travis-ci.org/kisielk/gotool.svg?branch=master)](https://travis-ci.org/kisielk/gotool)
|
||||||
|
|
||||||
|
Package gotool contains utility functions used to implement the standard "cmd/go" tool, provided as a convenience to developers who want to write tools with similar semantics.
|
1
vendor/github.com/kisielk/gotool/go.mod
generated
vendored
Normal file
1
vendor/github.com/kisielk/gotool/go.mod
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
module "github.com/kisielk/gotool"
|
15
vendor/github.com/kisielk/gotool/go13.go
generated
vendored
Normal file
15
vendor/github.com/kisielk/gotool/go13.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// +build !go1.4
|
||||||
|
|
||||||
|
package gotool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/build"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var gorootSrc = filepath.Join(runtime.GOROOT(), "src", "pkg")
|
||||||
|
|
||||||
|
func shouldIgnoreImport(p *build.Package) bool {
|
||||||
|
return true
|
||||||
|
}
|
15
vendor/github.com/kisielk/gotool/go14-15.go
generated
vendored
Normal file
15
vendor/github.com/kisielk/gotool/go14-15.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// +build go1.4,!go1.6
|
||||||
|
|
||||||
|
package gotool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/build"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var gorootSrc = filepath.Join(runtime.GOROOT(), "src")
|
||||||
|
|
||||||
|
func shouldIgnoreImport(p *build.Package) bool {
|
||||||
|
return true
|
||||||
|
}
|
15
vendor/github.com/kisielk/gotool/go16-18.go
generated
vendored
Normal file
15
vendor/github.com/kisielk/gotool/go16-18.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// +build go1.6,!go1.9
|
||||||
|
|
||||||
|
package gotool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/build"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var gorootSrc = filepath.Join(runtime.GOROOT(), "src")
|
||||||
|
|
||||||
|
func shouldIgnoreImport(p *build.Package) bool {
|
||||||
|
return p == nil || len(p.InvalidGoFiles) == 0
|
||||||
|
}
|
27
vendor/github.com/kisielk/gotool/internal/load/path.go
generated
vendored
Normal file
27
vendor/github.com/kisielk/gotool/internal/load/path.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build go1.9
|
||||||
|
|
||||||
|
package load
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hasPathPrefix reports whether the path s begins with the
|
||||||
|
// elements in prefix.
|
||||||
|
func hasPathPrefix(s, prefix string) bool {
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
case len(s) == len(prefix):
|
||||||
|
return s == prefix
|
||||||
|
case len(s) > len(prefix):
|
||||||
|
if prefix != "" && prefix[len(prefix)-1] == '/' {
|
||||||
|
return strings.HasPrefix(s, prefix)
|
||||||
|
}
|
||||||
|
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
|
||||||
|
}
|
||||||
|
}
|
25
vendor/github.com/kisielk/gotool/internal/load/pkg.go
generated
vendored
Normal file
25
vendor/github.com/kisielk/gotool/internal/load/pkg.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build go1.9
|
||||||
|
|
||||||
|
// Package load loads packages.
|
||||||
|
package load
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isStandardImportPath reports whether $GOROOT/src/path should be considered
|
||||||
|
// part of the standard distribution. For historical reasons we allow people to add
|
||||||
|
// their own code to $GOROOT instead of using $GOPATH, but we assume that
|
||||||
|
// code will start with a domain name (dot in the first element).
|
||||||
|
func isStandardImportPath(path string) bool {
|
||||||
|
i := strings.Index(path, "/")
|
||||||
|
if i < 0 {
|
||||||
|
i = len(path)
|
||||||
|
}
|
||||||
|
elem := path[:i]
|
||||||
|
return !strings.Contains(elem, ".")
|
||||||
|
}
|
354
vendor/github.com/kisielk/gotool/internal/load/search.go
generated
vendored
Normal file
354
vendor/github.com/kisielk/gotool/internal/load/search.go
generated
vendored
Normal file
|
@ -0,0 +1,354 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build go1.9
|
||||||
|
|
||||||
|
package load
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context specifies values for operation of ImportPaths that would
|
||||||
|
// otherwise come from cmd/go/internal/cfg package.
|
||||||
|
//
|
||||||
|
// This is a construct added for gotool purposes and doesn't have
|
||||||
|
// an equivalent upstream in cmd/go.
|
||||||
|
type Context struct {
|
||||||
|
// BuildContext is the build context to use.
|
||||||
|
BuildContext build.Context
|
||||||
|
|
||||||
|
// GOROOTsrc is the location of the src directory in GOROOT.
|
||||||
|
// At this time, it's used only in MatchPackages to skip
|
||||||
|
// GOOROOT/src entry from BuildContext.SrcDirs output.
|
||||||
|
GOROOTsrc string
|
||||||
|
}
|
||||||
|
|
||||||
|
// allPackages returns all the packages that can be found
|
||||||
|
// under the $GOPATH directories and $GOROOT matching pattern.
|
||||||
|
// The pattern is either "all" (all packages), "std" (standard packages),
|
||||||
|
// "cmd" (standard commands), or a path including "...".
|
||||||
|
func (c *Context) allPackages(pattern string) []string {
|
||||||
|
pkgs := c.MatchPackages(pattern)
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
||||||
|
}
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// allPackagesInFS is like allPackages but is passed a pattern
|
||||||
|
// beginning ./ or ../, meaning it should scan the tree rooted
|
||||||
|
// at the given directory. There are ... in the pattern too.
|
||||||
|
func (c *Context) allPackagesInFS(pattern string) []string {
|
||||||
|
pkgs := c.MatchPackagesInFS(pattern)
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
||||||
|
}
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchPackages returns a list of package paths matching pattern
|
||||||
|
// (see go help packages for pattern syntax).
|
||||||
|
func (c *Context) MatchPackages(pattern string) []string {
|
||||||
|
match := func(string) bool { return true }
|
||||||
|
treeCanMatch := func(string) bool { return true }
|
||||||
|
if !IsMetaPackage(pattern) {
|
||||||
|
match = matchPattern(pattern)
|
||||||
|
treeCanMatch = treeCanMatchPattern(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
have := map[string]bool{
|
||||||
|
"builtin": true, // ignore pseudo-package that exists only for documentation
|
||||||
|
}
|
||||||
|
if !c.BuildContext.CgoEnabled {
|
||||||
|
have["runtime/cgo"] = true // ignore during walk
|
||||||
|
}
|
||||||
|
var pkgs []string
|
||||||
|
|
||||||
|
for _, src := range c.BuildContext.SrcDirs() {
|
||||||
|
if (pattern == "std" || pattern == "cmd") && src != c.GOROOTsrc {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
src = filepath.Clean(src) + string(filepath.Separator)
|
||||||
|
root := src
|
||||||
|
if pattern == "cmd" {
|
||||||
|
root += "cmd" + string(filepath.Separator)
|
||||||
|
}
|
||||||
|
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil || path == src {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
want := true
|
||||||
|
// Avoid .foo, _foo, and testdata directory trees.
|
||||||
|
_, elem := filepath.Split(path)
|
||||||
|
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
||||||
|
want = false
|
||||||
|
}
|
||||||
|
|
||||||
|
name := filepath.ToSlash(path[len(src):])
|
||||||
|
if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
|
||||||
|
// The name "std" is only the standard library.
|
||||||
|
// If the name is cmd, it's the root of the command tree.
|
||||||
|
want = false
|
||||||
|
}
|
||||||
|
if !treeCanMatch(name) {
|
||||||
|
want = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fi.IsDir() {
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 && want {
|
||||||
|
if target, err := os.Stat(path); err == nil && target.IsDir() {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !want {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if have[name] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
have[name] = true
|
||||||
|
if !match(name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pkg, err := c.BuildContext.ImportDir(path, 0)
|
||||||
|
if err != nil {
|
||||||
|
if _, noGo := err.(*build.NoGoError); noGo {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are expanding "cmd", skip main
|
||||||
|
// packages under cmd/vendor. At least as of
|
||||||
|
// March, 2017, there is one there for the
|
||||||
|
// vendored pprof tool.
|
||||||
|
if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs = append(pkgs, name)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchPackagesInFS returns a list of package paths matching pattern,
|
||||||
|
// which must begin with ./ or ../
|
||||||
|
// (see go help packages for pattern syntax).
|
||||||
|
func (c *Context) MatchPackagesInFS(pattern string) []string {
|
||||||
|
// Find directory to begin the scan.
|
||||||
|
// Could be smarter but this one optimization
|
||||||
|
// is enough for now, since ... is usually at the
|
||||||
|
// end of a path.
|
||||||
|
i := strings.Index(pattern, "...")
|
||||||
|
dir, _ := path.Split(pattern[:i])
|
||||||
|
|
||||||
|
// pattern begins with ./ or ../.
|
||||||
|
// path.Clean will discard the ./ but not the ../.
|
||||||
|
// We need to preserve the ./ for pattern matching
|
||||||
|
// and in the returned import paths.
|
||||||
|
prefix := ""
|
||||||
|
if strings.HasPrefix(pattern, "./") {
|
||||||
|
prefix = "./"
|
||||||
|
}
|
||||||
|
match := matchPattern(pattern)
|
||||||
|
|
||||||
|
var pkgs []string
|
||||||
|
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil || !fi.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if path == dir {
|
||||||
|
// filepath.Walk starts at dir and recurses. For the recursive case,
|
||||||
|
// the path is the result of filepath.Join, which calls filepath.Clean.
|
||||||
|
// The initial case is not Cleaned, though, so we do this explicitly.
|
||||||
|
//
|
||||||
|
// This converts a path like "./io/" to "io". Without this step, running
|
||||||
|
// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
|
||||||
|
// package, because prepending the prefix "./" to the unclean path would
|
||||||
|
// result in "././io", and match("././io") returns false.
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
|
||||||
|
_, elem := filepath.Split(path)
|
||||||
|
dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
|
||||||
|
if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
name := prefix + filepath.ToSlash(path)
|
||||||
|
if !match(name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We keep the directory if we can import it, or if we can't import it
|
||||||
|
// due to invalid Go source files. This means that directories containing
|
||||||
|
// parse errors will be built (and fail) instead of being silently skipped
|
||||||
|
// as not matching the pattern. Go 1.5 and earlier skipped, but that
|
||||||
|
// behavior means people miss serious mistakes.
|
||||||
|
// See golang.org/issue/11407.
|
||||||
|
if p, err := c.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
|
||||||
|
if _, noGo := err.(*build.NoGoError); !noGo {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pkgs = append(pkgs, name)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// treeCanMatchPattern(pattern)(name) reports whether
|
||||||
|
// name or children of name can possibly match pattern.
|
||||||
|
// Pattern is the same limited glob accepted by matchPattern.
|
||||||
|
func treeCanMatchPattern(pattern string) func(name string) bool {
|
||||||
|
wildCard := false
|
||||||
|
if i := strings.Index(pattern, "..."); i >= 0 {
|
||||||
|
wildCard = true
|
||||||
|
pattern = pattern[:i]
|
||||||
|
}
|
||||||
|
return func(name string) bool {
|
||||||
|
return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
|
||||||
|
wildCard && strings.HasPrefix(name, pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchPattern(pattern)(name) reports whether
|
||||||
|
// name matches pattern. Pattern is a limited glob
|
||||||
|
// pattern in which '...' means 'any string' and there
|
||||||
|
// is no other special syntax.
|
||||||
|
// Unfortunately, there are two special cases. Quoting "go help packages":
|
||||||
|
//
|
||||||
|
// First, /... at the end of the pattern can match an empty string,
|
||||||
|
// so that net/... matches both net and packages in its subdirectories, like net/http.
|
||||||
|
// Second, any slash-separted pattern element containing a wildcard never
|
||||||
|
// participates in a match of the "vendor" element in the path of a vendored
|
||||||
|
// package, so that ./... does not match packages in subdirectories of
|
||||||
|
// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
|
||||||
|
// Note, however, that a directory named vendor that itself contains code
|
||||||
|
// is not a vendored package: cmd/vendor would be a command named vendor,
|
||||||
|
// and the pattern cmd/... matches it.
|
||||||
|
func matchPattern(pattern string) func(name string) bool {
|
||||||
|
// Convert pattern to regular expression.
|
||||||
|
// The strategy for the trailing /... is to nest it in an explicit ? expression.
|
||||||
|
// The strategy for the vendor exclusion is to change the unmatchable
|
||||||
|
// vendor strings to a disallowed code point (vendorChar) and to use
|
||||||
|
// "(anything but that codepoint)*" as the implementation of the ... wildcard.
|
||||||
|
// This is a bit complicated but the obvious alternative,
|
||||||
|
// namely a hand-written search like in most shell glob matchers,
|
||||||
|
// is too easy to make accidentally exponential.
|
||||||
|
// Using package regexp guarantees linear-time matching.
|
||||||
|
|
||||||
|
const vendorChar = "\x00"
|
||||||
|
|
||||||
|
if strings.Contains(pattern, vendorChar) {
|
||||||
|
return func(name string) bool { return false }
|
||||||
|
}
|
||||||
|
|
||||||
|
re := regexp.QuoteMeta(pattern)
|
||||||
|
re = replaceVendor(re, vendorChar)
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
|
||||||
|
re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
|
||||||
|
case re == vendorChar+`/\.\.\.`:
|
||||||
|
re = `(/vendor|/` + vendorChar + `/\.\.\.)`
|
||||||
|
case strings.HasSuffix(re, `/\.\.\.`):
|
||||||
|
re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
|
||||||
|
}
|
||||||
|
re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1)
|
||||||
|
|
||||||
|
reg := regexp.MustCompile(`^` + re + `$`)
|
||||||
|
|
||||||
|
return func(name string) bool {
|
||||||
|
if strings.Contains(name, vendorChar) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return reg.MatchString(replaceVendor(name, vendorChar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceVendor returns the result of replacing
|
||||||
|
// non-trailing vendor path elements in x with repl.
|
||||||
|
func replaceVendor(x, repl string) string {
|
||||||
|
if !strings.Contains(x, "vendor") {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
elem := strings.Split(x, "/")
|
||||||
|
for i := 0; i < len(elem)-1; i++ {
|
||||||
|
if elem[i] == "vendor" {
|
||||||
|
elem[i] = repl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(elem, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportPaths returns the import paths to use for the given command line.
|
||||||
|
func (c *Context) ImportPaths(args []string) []string {
|
||||||
|
args = c.ImportPathsNoDotExpansion(args)
|
||||||
|
var out []string
|
||||||
|
for _, a := range args {
|
||||||
|
if strings.Contains(a, "...") {
|
||||||
|
if build.IsLocalImport(a) {
|
||||||
|
out = append(out, c.allPackagesInFS(a)...)
|
||||||
|
} else {
|
||||||
|
out = append(out, c.allPackages(a)...)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, a)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportPathsNoDotExpansion returns the import paths to use for the given
|
||||||
|
// command line, but it does no ... expansion.
|
||||||
|
func (c *Context) ImportPathsNoDotExpansion(args []string) []string {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return []string{"."}
|
||||||
|
}
|
||||||
|
var out []string
|
||||||
|
for _, a := range args {
|
||||||
|
// Arguments are supposed to be import paths, but
|
||||||
|
// as a courtesy to Windows developers, rewrite \ to /
|
||||||
|
// in command-line arguments. Handles .\... and so on.
|
||||||
|
if filepath.Separator == '\\' {
|
||||||
|
a = strings.Replace(a, `\`, `/`, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put argument in canonical form, but preserve leading ./.
|
||||||
|
if strings.HasPrefix(a, "./") {
|
||||||
|
a = "./" + path.Clean(a)
|
||||||
|
if a == "./." {
|
||||||
|
a = "."
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a = path.Clean(a)
|
||||||
|
}
|
||||||
|
if IsMetaPackage(a) {
|
||||||
|
out = append(out, c.allPackages(a)...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, a)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
|
||||||
|
func IsMetaPackage(name string) bool {
|
||||||
|
return name == "std" || name == "cmd" || name == "all"
|
||||||
|
}
|
56
vendor/github.com/kisielk/gotool/match.go
generated
vendored
Normal file
56
vendor/github.com/kisielk/gotool/match.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build go1.9
|
||||||
|
|
||||||
|
package gotool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/kisielk/gotool/internal/load"
|
||||||
|
)
|
||||||
|
|
||||||
|
// importPaths returns the import paths to use for the given command line.
|
||||||
|
func (c *Context) importPaths(args []string) []string {
|
||||||
|
lctx := load.Context{
|
||||||
|
BuildContext: c.BuildContext,
|
||||||
|
GOROOTsrc: c.joinPath(c.BuildContext.GOROOT, "src"),
|
||||||
|
}
|
||||||
|
return lctx.ImportPaths(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// joinPath calls c.BuildContext.JoinPath (if not nil) or else filepath.Join.
|
||||||
|
//
|
||||||
|
// It's a copy of the unexported build.Context.joinPath helper.
|
||||||
|
func (c *Context) joinPath(elem ...string) string {
|
||||||
|
if f := c.BuildContext.JoinPath; f != nil {
|
||||||
|
return f(elem...)
|
||||||
|
}
|
||||||
|
return filepath.Join(elem...)
|
||||||
|
}
|
317
vendor/github.com/kisielk/gotool/match18.go
generated
vendored
Normal file
317
vendor/github.com/kisielk/gotool/match18.go
generated
vendored
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build !go1.9
|
||||||
|
|
||||||
|
package gotool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file contains code from the Go distribution.
|
||||||
|
|
||||||
|
// matchPattern(pattern)(name) reports whether
|
||||||
|
// name matches pattern. Pattern is a limited glob
|
||||||
|
// pattern in which '...' means 'any string' and there
|
||||||
|
// is no other special syntax.
|
||||||
|
func matchPattern(pattern string) func(name string) bool {
|
||||||
|
re := regexp.QuoteMeta(pattern)
|
||||||
|
re = strings.Replace(re, `\.\.\.`, `.*`, -1)
|
||||||
|
// Special case: foo/... matches foo too.
|
||||||
|
if strings.HasSuffix(re, `/.*`) {
|
||||||
|
re = re[:len(re)-len(`/.*`)] + `(/.*)?`
|
||||||
|
}
|
||||||
|
reg := regexp.MustCompile(`^` + re + `$`)
|
||||||
|
return reg.MatchString
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchPackages returns a list of package paths matching pattern
|
||||||
|
// (see go help packages for pattern syntax).
|
||||||
|
func (c *Context) matchPackages(pattern string) []string {
|
||||||
|
match := func(string) bool { return true }
|
||||||
|
treeCanMatch := func(string) bool { return true }
|
||||||
|
if !isMetaPackage(pattern) {
|
||||||
|
match = matchPattern(pattern)
|
||||||
|
treeCanMatch = treeCanMatchPattern(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
have := map[string]bool{
|
||||||
|
"builtin": true, // ignore pseudo-package that exists only for documentation
|
||||||
|
}
|
||||||
|
if !c.BuildContext.CgoEnabled {
|
||||||
|
have["runtime/cgo"] = true // ignore during walk
|
||||||
|
}
|
||||||
|
var pkgs []string
|
||||||
|
|
||||||
|
for _, src := range c.BuildContext.SrcDirs() {
|
||||||
|
if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
src = filepath.Clean(src) + string(filepath.Separator)
|
||||||
|
root := src
|
||||||
|
if pattern == "cmd" {
|
||||||
|
root += "cmd" + string(filepath.Separator)
|
||||||
|
}
|
||||||
|
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil || !fi.IsDir() || path == src {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid .foo, _foo, and testdata directory trees.
|
||||||
|
_, elem := filepath.Split(path)
|
||||||
|
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
name := filepath.ToSlash(path[len(src):])
|
||||||
|
if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
|
||||||
|
// The name "std" is only the standard library.
|
||||||
|
// If the name is cmd, it's the root of the command tree.
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
if !treeCanMatch(name) {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
if have[name] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
have[name] = true
|
||||||
|
if !match(name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = c.BuildContext.ImportDir(path, 0)
|
||||||
|
if err != nil {
|
||||||
|
if _, noGo := err.(*build.NoGoError); noGo {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pkgs = append(pkgs, name)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// importPathsNoDotExpansion returns the import paths to use for the given
|
||||||
|
// command line, but it does no ... expansion.
|
||||||
|
func (c *Context) importPathsNoDotExpansion(args []string) []string {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return []string{"."}
|
||||||
|
}
|
||||||
|
var out []string
|
||||||
|
for _, a := range args {
|
||||||
|
// Arguments are supposed to be import paths, but
|
||||||
|
// as a courtesy to Windows developers, rewrite \ to /
|
||||||
|
// in command-line arguments. Handles .\... and so on.
|
||||||
|
if filepath.Separator == '\\' {
|
||||||
|
a = strings.Replace(a, `\`, `/`, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put argument in canonical form, but preserve leading ./.
|
||||||
|
if strings.HasPrefix(a, "./") {
|
||||||
|
a = "./" + path.Clean(a)
|
||||||
|
if a == "./." {
|
||||||
|
a = "."
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a = path.Clean(a)
|
||||||
|
}
|
||||||
|
if isMetaPackage(a) {
|
||||||
|
out = append(out, c.allPackages(a)...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, a)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// importPaths returns the import paths to use for the given command line.
|
||||||
|
func (c *Context) importPaths(args []string) []string {
|
||||||
|
args = c.importPathsNoDotExpansion(args)
|
||||||
|
var out []string
|
||||||
|
for _, a := range args {
|
||||||
|
if strings.Contains(a, "...") {
|
||||||
|
if build.IsLocalImport(a) {
|
||||||
|
out = append(out, c.allPackagesInFS(a)...)
|
||||||
|
} else {
|
||||||
|
out = append(out, c.allPackages(a)...)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, a)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// allPackages returns all the packages that can be found
|
||||||
|
// under the $GOPATH directories and $GOROOT matching pattern.
|
||||||
|
// The pattern is either "all" (all packages), "std" (standard packages),
|
||||||
|
// "cmd" (standard commands), or a path including "...".
|
||||||
|
func (c *Context) allPackages(pattern string) []string {
|
||||||
|
pkgs := c.matchPackages(pattern)
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
||||||
|
}
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// allPackagesInFS is like allPackages but is passed a pattern
|
||||||
|
// beginning ./ or ../, meaning it should scan the tree rooted
|
||||||
|
// at the given directory. There are ... in the pattern too.
|
||||||
|
func (c *Context) allPackagesInFS(pattern string) []string {
|
||||||
|
pkgs := c.matchPackagesInFS(pattern)
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
||||||
|
}
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchPackagesInFS returns a list of package paths matching pattern,
|
||||||
|
// which must begin with ./ or ../
|
||||||
|
// (see go help packages for pattern syntax).
|
||||||
|
func (c *Context) matchPackagesInFS(pattern string) []string {
|
||||||
|
// Find directory to begin the scan.
|
||||||
|
// Could be smarter but this one optimization
|
||||||
|
// is enough for now, since ... is usually at the
|
||||||
|
// end of a path.
|
||||||
|
i := strings.Index(pattern, "...")
|
||||||
|
dir, _ := path.Split(pattern[:i])
|
||||||
|
|
||||||
|
// pattern begins with ./ or ../.
|
||||||
|
// path.Clean will discard the ./ but not the ../.
|
||||||
|
// We need to preserve the ./ for pattern matching
|
||||||
|
// and in the returned import paths.
|
||||||
|
prefix := ""
|
||||||
|
if strings.HasPrefix(pattern, "./") {
|
||||||
|
prefix = "./"
|
||||||
|
}
|
||||||
|
match := matchPattern(pattern)
|
||||||
|
|
||||||
|
var pkgs []string
|
||||||
|
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil || !fi.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if path == dir {
|
||||||
|
// filepath.Walk starts at dir and recurses. For the recursive case,
|
||||||
|
// the path is the result of filepath.Join, which calls filepath.Clean.
|
||||||
|
// The initial case is not Cleaned, though, so we do this explicitly.
|
||||||
|
//
|
||||||
|
// This converts a path like "./io/" to "io". Without this step, running
|
||||||
|
// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
|
||||||
|
// package, because prepending the prefix "./" to the unclean path would
|
||||||
|
// result in "././io", and match("././io") returns false.
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
|
||||||
|
_, elem := filepath.Split(path)
|
||||||
|
dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
|
||||||
|
if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
name := prefix + filepath.ToSlash(path)
|
||||||
|
if !match(name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We keep the directory if we can import it, or if we can't import it
|
||||||
|
// due to invalid Go source files. This means that directories containing
|
||||||
|
// parse errors will be built (and fail) instead of being silently skipped
|
||||||
|
// as not matching the pattern. Go 1.5 and earlier skipped, but that
|
||||||
|
// behavior means people miss serious mistakes.
|
||||||
|
// See golang.org/issue/11407.
|
||||||
|
if p, err := c.BuildContext.ImportDir(path, 0); err != nil && shouldIgnoreImport(p) {
|
||||||
|
if _, noGo := err.(*build.NoGoError); !noGo {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pkgs = append(pkgs, name)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// isMetaPackage checks if name is a reserved package name that expands to multiple packages.
|
||||||
|
func isMetaPackage(name string) bool {
|
||||||
|
return name == "std" || name == "cmd" || name == "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
// isStandardImportPath reports whether $GOROOT/src/path should be considered
|
||||||
|
// part of the standard distribution. For historical reasons we allow people to add
|
||||||
|
// their own code to $GOROOT instead of using $GOPATH, but we assume that
|
||||||
|
// code will start with a domain name (dot in the first element).
|
||||||
|
func isStandardImportPath(path string) bool {
|
||||||
|
i := strings.Index(path, "/")
|
||||||
|
if i < 0 {
|
||||||
|
i = len(path)
|
||||||
|
}
|
||||||
|
elem := path[:i]
|
||||||
|
return !strings.Contains(elem, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasPathPrefix reports whether the path s begins with the
|
||||||
|
// elements in prefix.
|
||||||
|
func hasPathPrefix(s, prefix string) bool {
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
case len(s) == len(prefix):
|
||||||
|
return s == prefix
|
||||||
|
case len(s) > len(prefix):
|
||||||
|
if prefix != "" && prefix[len(prefix)-1] == '/' {
|
||||||
|
return strings.HasPrefix(s, prefix)
|
||||||
|
}
|
||||||
|
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// treeCanMatchPattern(pattern)(name) reports whether
|
||||||
|
// name or children of name can possibly match pattern.
|
||||||
|
// Pattern is the same limited glob accepted by matchPattern.
|
||||||
|
func treeCanMatchPattern(pattern string) func(name string) bool {
|
||||||
|
wildCard := false
|
||||||
|
if i := strings.Index(pattern, "..."); i >= 0 {
|
||||||
|
wildCard = true
|
||||||
|
pattern = pattern[:i]
|
||||||
|
}
|
||||||
|
return func(name string) bool {
|
||||||
|
return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
|
||||||
|
wildCard && strings.HasPrefix(name, pattern)
|
||||||
|
}
|
||||||
|
}
|
48
vendor/github.com/kisielk/gotool/tool.go
generated
vendored
Normal file
48
vendor/github.com/kisielk/gotool/tool.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// Package gotool contains utility functions used to implement the standard
|
||||||
|
// "cmd/go" tool, provided as a convenience to developers who want to write
|
||||||
|
// tools with similar semantics.
|
||||||
|
package gotool
|
||||||
|
|
||||||
|
import "go/build"
|
||||||
|
|
||||||
|
// Export functions here to make it easier to keep the implementations up to date with upstream.
|
||||||
|
|
||||||
|
// DefaultContext is the default context that uses build.Default.
|
||||||
|
var DefaultContext = Context{
|
||||||
|
BuildContext: build.Default,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Context specifies the supporting context.
|
||||||
|
type Context struct {
|
||||||
|
// BuildContext is the build.Context that is used when computing import paths.
|
||||||
|
BuildContext build.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportPaths returns the import paths to use for the given command line.
|
||||||
|
//
|
||||||
|
// The path "all" is expanded to all packages in $GOPATH and $GOROOT.
|
||||||
|
// The path "std" is expanded to all packages in the Go standard library.
|
||||||
|
// The path "cmd" is expanded to all Go standard commands.
|
||||||
|
// The string "..." is treated as a wildcard within a path.
|
||||||
|
// When matching recursively, directories are ignored if they are prefixed with
|
||||||
|
// a dot or an underscore (such as ".foo" or "_foo"), or are named "testdata".
|
||||||
|
// Relative import paths are not converted to full import paths.
|
||||||
|
// If args is empty, a single element "." is returned.
|
||||||
|
func (c *Context) ImportPaths(args []string) []string {
|
||||||
|
return c.importPaths(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportPaths returns the import paths to use for the given command line
|
||||||
|
// using default context.
|
||||||
|
//
|
||||||
|
// The path "all" is expanded to all packages in $GOPATH and $GOROOT.
|
||||||
|
// The path "std" is expanded to all packages in the Go standard library.
|
||||||
|
// The path "cmd" is expanded to all Go standard commands.
|
||||||
|
// The string "..." is treated as a wildcard within a path.
|
||||||
|
// When matching recursively, directories are ignored if they are prefixed with
|
||||||
|
// a dot or an underscore (such as ".foo" or "_foo"), or are named "testdata".
|
||||||
|
// Relative import paths are not converted to full import paths.
|
||||||
|
// If args is empty, a single element "." is returned.
|
||||||
|
func ImportPaths(args []string) []string {
|
||||||
|
return DefaultContext.importPaths(args)
|
||||||
|
}
|
46
vendor/golang.org/x/tools/go/types/typeutil/callee.go
generated
vendored
Normal file
46
vendor/golang.org/x/tools/go/types/typeutil/callee.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// 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 typeutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/types"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Callee returns the named target of a function call, if any:
|
||||||
|
// a function, method, builtin, or variable.
|
||||||
|
func Callee(info *types.Info, call *ast.CallExpr) types.Object {
|
||||||
|
var obj types.Object
|
||||||
|
switch fun := astutil.Unparen(call.Fun).(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
obj = info.Uses[fun] // type, var, builtin, or declared func
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
if sel, ok := info.Selections[fun]; ok {
|
||||||
|
obj = sel.Obj() // method or field
|
||||||
|
} else {
|
||||||
|
obj = info.Uses[fun.Sel] // qualified identifier?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := obj.(*types.TypeName); ok {
|
||||||
|
return nil // T(x) is a conversion, not a call
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticCallee returns the target (function or method) of a static
|
||||||
|
// function call, if any. It returns nil for calls to builtins.
|
||||||
|
func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
|
||||||
|
if f, ok := Callee(info, call).(*types.Func); ok && !interfaceMethod(f) {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func interfaceMethod(f *types.Func) bool {
|
||||||
|
recv := f.Type().(*types.Signature).Recv()
|
||||||
|
return recv != nil && types.IsInterface(recv.Type())
|
||||||
|
}
|
31
vendor/golang.org/x/tools/go/types/typeutil/imports.go
generated
vendored
Normal file
31
vendor/golang.org/x/tools/go/types/typeutil/imports.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2014 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 typeutil
|
||||||
|
|
||||||
|
import "go/types"
|
||||||
|
|
||||||
|
// Dependencies returns all dependencies of the specified packages.
|
||||||
|
//
|
||||||
|
// Dependent packages appear in topological order: if package P imports
|
||||||
|
// package Q, Q appears earlier than P in the result.
|
||||||
|
// The algorithm follows import statements in the order they
|
||||||
|
// appear in the source code, so the result is a total order.
|
||||||
|
//
|
||||||
|
func Dependencies(pkgs ...*types.Package) []*types.Package {
|
||||||
|
var result []*types.Package
|
||||||
|
seen := make(map[*types.Package]bool)
|
||||||
|
var visit func(pkgs []*types.Package)
|
||||||
|
visit = func(pkgs []*types.Package) {
|
||||||
|
for _, p := range pkgs {
|
||||||
|
if !seen[p] {
|
||||||
|
seen[p] = true
|
||||||
|
visit(p.Imports())
|
||||||
|
result = append(result, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visit(pkgs)
|
||||||
|
return result
|
||||||
|
}
|
313
vendor/golang.org/x/tools/go/types/typeutil/map.go
generated
vendored
Normal file
313
vendor/golang.org/x/tools/go/types/typeutil/map.go
generated
vendored
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
// Copyright 2014 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 typeutil defines various utilities for types, such as Map,
|
||||||
|
// a mapping from types.Type to interface{} values.
|
||||||
|
package typeutil // import "golang.org/x/tools/go/types/typeutil"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/types"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map is a hash-table-based mapping from types (types.Type) to
|
||||||
|
// arbitrary interface{} values. The concrete types that implement
|
||||||
|
// the Type interface are pointers. Since they are not canonicalized,
|
||||||
|
// == cannot be used to check for equivalence, and thus we cannot
|
||||||
|
// simply use a Go map.
|
||||||
|
//
|
||||||
|
// Just as with map[K]V, a nil *Map is a valid empty map.
|
||||||
|
//
|
||||||
|
// Not thread-safe.
|
||||||
|
//
|
||||||
|
type Map struct {
|
||||||
|
hasher Hasher // shared by many Maps
|
||||||
|
table map[uint32][]entry // maps hash to bucket; entry.key==nil means unused
|
||||||
|
length int // number of map entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// entry is an entry (key/value association) in a hash bucket.
|
||||||
|
type entry struct {
|
||||||
|
key types.Type
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHasher sets the hasher used by Map.
|
||||||
|
//
|
||||||
|
// All Hashers are functionally equivalent but contain internal state
|
||||||
|
// used to cache the results of hashing previously seen types.
|
||||||
|
//
|
||||||
|
// A single Hasher created by MakeHasher() may be shared among many
|
||||||
|
// Maps. This is recommended if the instances have many keys in
|
||||||
|
// common, as it will amortize the cost of hash computation.
|
||||||
|
//
|
||||||
|
// A Hasher may grow without bound as new types are seen. Even when a
|
||||||
|
// type is deleted from the map, the Hasher never shrinks, since other
|
||||||
|
// types in the map may reference the deleted type indirectly.
|
||||||
|
//
|
||||||
|
// Hashers are not thread-safe, and read-only operations such as
|
||||||
|
// Map.Lookup require updates to the hasher, so a full Mutex lock (not a
|
||||||
|
// read-lock) is require around all Map operations if a shared
|
||||||
|
// hasher is accessed from multiple threads.
|
||||||
|
//
|
||||||
|
// If SetHasher is not called, the Map will create a private hasher at
|
||||||
|
// the first call to Insert.
|
||||||
|
//
|
||||||
|
func (m *Map) SetHasher(hasher Hasher) {
|
||||||
|
m.hasher = hasher
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the entry with the given key, if any.
|
||||||
|
// It returns true if the entry was found.
|
||||||
|
//
|
||||||
|
func (m *Map) Delete(key types.Type) bool {
|
||||||
|
if m != nil && m.table != nil {
|
||||||
|
hash := m.hasher.Hash(key)
|
||||||
|
bucket := m.table[hash]
|
||||||
|
for i, e := range bucket {
|
||||||
|
if e.key != nil && types.Identical(key, e.key) {
|
||||||
|
// We can't compact the bucket as it
|
||||||
|
// would disturb iterators.
|
||||||
|
bucket[i] = entry{}
|
||||||
|
m.length--
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// At returns the map entry for the given key.
|
||||||
|
// The result is nil if the entry is not present.
|
||||||
|
//
|
||||||
|
func (m *Map) At(key types.Type) interface{} {
|
||||||
|
if m != nil && m.table != nil {
|
||||||
|
for _, e := range m.table[m.hasher.Hash(key)] {
|
||||||
|
if e.key != nil && types.Identical(key, e.key) {
|
||||||
|
return e.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the map entry for key to val,
|
||||||
|
// and returns the previous entry, if any.
|
||||||
|
func (m *Map) Set(key types.Type, value interface{}) (prev interface{}) {
|
||||||
|
if m.table != nil {
|
||||||
|
hash := m.hasher.Hash(key)
|
||||||
|
bucket := m.table[hash]
|
||||||
|
var hole *entry
|
||||||
|
for i, e := range bucket {
|
||||||
|
if e.key == nil {
|
||||||
|
hole = &bucket[i]
|
||||||
|
} else if types.Identical(key, e.key) {
|
||||||
|
prev = e.value
|
||||||
|
bucket[i].value = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hole != nil {
|
||||||
|
*hole = entry{key, value} // overwrite deleted entry
|
||||||
|
} else {
|
||||||
|
m.table[hash] = append(bucket, entry{key, value})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if m.hasher.memo == nil {
|
||||||
|
m.hasher = MakeHasher()
|
||||||
|
}
|
||||||
|
hash := m.hasher.Hash(key)
|
||||||
|
m.table = map[uint32][]entry{hash: {entry{key, value}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.length++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of map entries.
|
||||||
|
func (m *Map) Len() int {
|
||||||
|
if m != nil {
|
||||||
|
return m.length
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate calls function f on each entry in the map in unspecified order.
|
||||||
|
//
|
||||||
|
// If f should mutate the map, Iterate provides the same guarantees as
|
||||||
|
// Go maps: if f deletes a map entry that Iterate has not yet reached,
|
||||||
|
// f will not be invoked for it, but if f inserts a map entry that
|
||||||
|
// Iterate has not yet reached, whether or not f will be invoked for
|
||||||
|
// it is unspecified.
|
||||||
|
//
|
||||||
|
func (m *Map) Iterate(f func(key types.Type, value interface{})) {
|
||||||
|
if m != nil {
|
||||||
|
for _, bucket := range m.table {
|
||||||
|
for _, e := range bucket {
|
||||||
|
if e.key != nil {
|
||||||
|
f(e.key, e.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns a new slice containing the set of map keys.
|
||||||
|
// The order is unspecified.
|
||||||
|
func (m *Map) Keys() []types.Type {
|
||||||
|
keys := make([]types.Type, 0, m.Len())
|
||||||
|
m.Iterate(func(key types.Type, _ interface{}) {
|
||||||
|
keys = append(keys, key)
|
||||||
|
})
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Map) toString(values bool) string {
|
||||||
|
if m == nil {
|
||||||
|
return "{}"
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprint(&buf, "{")
|
||||||
|
sep := ""
|
||||||
|
m.Iterate(func(key types.Type, value interface{}) {
|
||||||
|
fmt.Fprint(&buf, sep)
|
||||||
|
sep = ", "
|
||||||
|
fmt.Fprint(&buf, key)
|
||||||
|
if values {
|
||||||
|
fmt.Fprintf(&buf, ": %q", value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
fmt.Fprint(&buf, "}")
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the map's entries.
|
||||||
|
// Values are printed using fmt.Sprintf("%v", v).
|
||||||
|
// Order is unspecified.
|
||||||
|
//
|
||||||
|
func (m *Map) String() string {
|
||||||
|
return m.toString(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeysString returns a string representation of the map's key set.
|
||||||
|
// Order is unspecified.
|
||||||
|
//
|
||||||
|
func (m *Map) KeysString() string {
|
||||||
|
return m.toString(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Hasher
|
||||||
|
|
||||||
|
// A Hasher maps each type to its hash value.
|
||||||
|
// For efficiency, a hasher uses memoization; thus its memory
|
||||||
|
// footprint grows monotonically over time.
|
||||||
|
// Hashers are not thread-safe.
|
||||||
|
// Hashers have reference semantics.
|
||||||
|
// Call MakeHasher to create a Hasher.
|
||||||
|
type Hasher struct {
|
||||||
|
memo map[types.Type]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeHasher returns a new Hasher instance.
|
||||||
|
func MakeHasher() Hasher {
|
||||||
|
return Hasher{make(map[types.Type]uint32)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash computes a hash value for the given type t such that
|
||||||
|
// Identical(t, t') => Hash(t) == Hash(t').
|
||||||
|
func (h Hasher) Hash(t types.Type) uint32 {
|
||||||
|
hash, ok := h.memo[t]
|
||||||
|
if !ok {
|
||||||
|
hash = h.hashFor(t)
|
||||||
|
h.memo[t] = hash
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashString computes the Fowler–Noll–Vo hash of s.
|
||||||
|
func hashString(s string) uint32 {
|
||||||
|
var h uint32
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
h ^= uint32(s[i])
|
||||||
|
h *= 16777619
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashFor computes the hash of t.
|
||||||
|
func (h Hasher) hashFor(t types.Type) uint32 {
|
||||||
|
// See Identical for rationale.
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.Basic:
|
||||||
|
return uint32(t.Kind())
|
||||||
|
|
||||||
|
case *types.Array:
|
||||||
|
return 9043 + 2*uint32(t.Len()) + 3*h.Hash(t.Elem())
|
||||||
|
|
||||||
|
case *types.Slice:
|
||||||
|
return 9049 + 2*h.Hash(t.Elem())
|
||||||
|
|
||||||
|
case *types.Struct:
|
||||||
|
var hash uint32 = 9059
|
||||||
|
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
if f.Anonymous() {
|
||||||
|
hash += 8861
|
||||||
|
}
|
||||||
|
hash += hashString(t.Tag(i))
|
||||||
|
hash += hashString(f.Name()) // (ignore f.Pkg)
|
||||||
|
hash += h.Hash(f.Type())
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
|
||||||
|
case *types.Pointer:
|
||||||
|
return 9067 + 2*h.Hash(t.Elem())
|
||||||
|
|
||||||
|
case *types.Signature:
|
||||||
|
var hash uint32 = 9091
|
||||||
|
if t.Variadic() {
|
||||||
|
hash *= 8863
|
||||||
|
}
|
||||||
|
return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results())
|
||||||
|
|
||||||
|
case *types.Interface:
|
||||||
|
var hash uint32 = 9103
|
||||||
|
for i, n := 0, t.NumMethods(); i < n; i++ {
|
||||||
|
// See go/types.identicalMethods for rationale.
|
||||||
|
// Method order is not significant.
|
||||||
|
// Ignore m.Pkg().
|
||||||
|
m := t.Method(i)
|
||||||
|
hash += 3*hashString(m.Name()) + 5*h.Hash(m.Type())
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
|
||||||
|
case *types.Map:
|
||||||
|
return 9109 + 2*h.Hash(t.Key()) + 3*h.Hash(t.Elem())
|
||||||
|
|
||||||
|
case *types.Chan:
|
||||||
|
return 9127 + 2*uint32(t.Dir()) + 3*h.Hash(t.Elem())
|
||||||
|
|
||||||
|
case *types.Named:
|
||||||
|
// Not safe with a copying GC; objects may move.
|
||||||
|
return uint32(reflect.ValueOf(t.Obj()).Pointer())
|
||||||
|
|
||||||
|
case *types.Tuple:
|
||||||
|
return h.hashTuple(t)
|
||||||
|
}
|
||||||
|
panic(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Hasher) hashTuple(tuple *types.Tuple) uint32 {
|
||||||
|
// See go/types.identicalTypes for rationale.
|
||||||
|
n := tuple.Len()
|
||||||
|
var hash uint32 = 9137 + 2*uint32(n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
hash += 3 * h.Hash(tuple.At(i).Type())
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
72
vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go
generated
vendored
Normal file
72
vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2014 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 implements a cache of method sets.
|
||||||
|
|
||||||
|
package typeutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/types"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A MethodSetCache records the method set of each type T for which
|
||||||
|
// MethodSet(T) is called so that repeat queries are fast.
|
||||||
|
// The zero value is a ready-to-use cache instance.
|
||||||
|
type MethodSetCache struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
named map[*types.Named]struct{ value, pointer *types.MethodSet } // method sets for named N and *N
|
||||||
|
others map[types.Type]*types.MethodSet // all other types
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodSet returns the method set of type T. It is thread-safe.
|
||||||
|
//
|
||||||
|
// If cache is nil, this function is equivalent to types.NewMethodSet(T).
|
||||||
|
// Utility functions can thus expose an optional *MethodSetCache
|
||||||
|
// parameter to clients that care about performance.
|
||||||
|
//
|
||||||
|
func (cache *MethodSetCache) MethodSet(T types.Type) *types.MethodSet {
|
||||||
|
if cache == nil {
|
||||||
|
return types.NewMethodSet(T)
|
||||||
|
}
|
||||||
|
cache.mu.Lock()
|
||||||
|
defer cache.mu.Unlock()
|
||||||
|
|
||||||
|
switch T := T.(type) {
|
||||||
|
case *types.Named:
|
||||||
|
return cache.lookupNamed(T).value
|
||||||
|
|
||||||
|
case *types.Pointer:
|
||||||
|
if N, ok := T.Elem().(*types.Named); ok {
|
||||||
|
return cache.lookupNamed(N).pointer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all other types
|
||||||
|
// (The map uses pointer equivalence, not type identity.)
|
||||||
|
mset := cache.others[T]
|
||||||
|
if mset == nil {
|
||||||
|
mset = types.NewMethodSet(T)
|
||||||
|
if cache.others == nil {
|
||||||
|
cache.others = make(map[types.Type]*types.MethodSet)
|
||||||
|
}
|
||||||
|
cache.others[T] = mset
|
||||||
|
}
|
||||||
|
return mset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *MethodSetCache) lookupNamed(named *types.Named) struct{ value, pointer *types.MethodSet } {
|
||||||
|
if cache.named == nil {
|
||||||
|
cache.named = make(map[*types.Named]struct{ value, pointer *types.MethodSet })
|
||||||
|
}
|
||||||
|
// Avoid recomputing mset(*T) for each distinct Pointer
|
||||||
|
// instance whose underlying type is a named type.
|
||||||
|
msets, ok := cache.named[named]
|
||||||
|
if !ok {
|
||||||
|
msets.value = types.NewMethodSet(named)
|
||||||
|
msets.pointer = types.NewMethodSet(types.NewPointer(named))
|
||||||
|
cache.named[named] = msets
|
||||||
|
}
|
||||||
|
return msets
|
||||||
|
}
|
52
vendor/golang.org/x/tools/go/types/typeutil/ui.go
generated
vendored
Normal file
52
vendor/golang.org/x/tools/go/types/typeutil/ui.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2014 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 typeutil
|
||||||
|
|
||||||
|
// This file defines utilities for user interfaces that display types.
|
||||||
|
|
||||||
|
import "go/types"
|
||||||
|
|
||||||
|
// IntuitiveMethodSet returns the intuitive method set of a type T,
|
||||||
|
// which is the set of methods you can call on an addressable value of
|
||||||
|
// that type.
|
||||||
|
//
|
||||||
|
// The result always contains MethodSet(T), and is exactly MethodSet(T)
|
||||||
|
// for interface types and for pointer-to-concrete types.
|
||||||
|
// For all other concrete types T, the result additionally
|
||||||
|
// contains each method belonging to *T if there is no identically
|
||||||
|
// named method on T itself.
|
||||||
|
//
|
||||||
|
// This corresponds to user intuition about method sets;
|
||||||
|
// this function is intended only for user interfaces.
|
||||||
|
//
|
||||||
|
// The order of the result is as for types.MethodSet(T).
|
||||||
|
//
|
||||||
|
func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection {
|
||||||
|
isPointerToConcrete := func(T types.Type) bool {
|
||||||
|
ptr, ok := T.(*types.Pointer)
|
||||||
|
return ok && !types.IsInterface(ptr.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []*types.Selection
|
||||||
|
mset := msets.MethodSet(T)
|
||||||
|
if types.IsInterface(T) || isPointerToConcrete(T) {
|
||||||
|
for i, n := 0, mset.Len(); i < n; i++ {
|
||||||
|
result = append(result, mset.At(i))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// T is some other concrete type.
|
||||||
|
// Report methods of T and *T, preferring those of T.
|
||||||
|
pmset := msets.MethodSet(types.NewPointer(T))
|
||||||
|
for i, n := 0, pmset.Len(); i < n; i++ {
|
||||||
|
meth := pmset.At(i)
|
||||||
|
if m := mset.Lookup(meth.Obj().Pkg(), meth.Obj().Name()); m != nil {
|
||||||
|
meth = m
|
||||||
|
}
|
||||||
|
result = append(result, meth)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
20
vendor/honnef.co/go/tools/LICENSE
vendored
Normal file
20
vendor/honnef.co/go/tools/LICENSE
vendored
Normal file
|
@ -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.
|
15
vendor/honnef.co/go/tools/cmd/gosimple/README.md
vendored
Normal file
15
vendor/honnef.co/go/tools/cmd/gosimple/README.md
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# gosimple
|
||||||
|
|
||||||
|
_gosimple_ is a linter for Go source code that specialises on
|
||||||
|
simplifying code.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Gosimple requires Go 1.6 or later.
|
||||||
|
|
||||||
|
go get honnef.co/go/tools/cmd/gosimple
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Detailed documentation can be found on
|
||||||
|
[staticcheck.io](https://staticcheck.io/docs/gosimple).
|
21
vendor/honnef.co/go/tools/cmd/gosimple/gosimple.go
vendored
Normal file
21
vendor/honnef.co/go/tools/cmd/gosimple/gosimple.go
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// gosimple detects code that could be rewritten in a simpler way.
|
||||||
|
package main // import "honnef.co/go/tools/cmd/gosimple"
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"honnef.co/go/tools/lint/lintutil"
|
||||||
|
"honnef.co/go/tools/simple"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fs := lintutil.FlagSet("gosimple")
|
||||||
|
gen := fs.Bool("generated", false, "Check generated code")
|
||||||
|
fs.Parse(os.Args[1:])
|
||||||
|
c := simple.NewChecker()
|
||||||
|
c.CheckGenerated = *gen
|
||||||
|
cfg := lintutil.CheckerConfig{
|
||||||
|
Checker: c,
|
||||||
|
ExitNonZero: true,
|
||||||
|
}
|
||||||
|
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
|
||||||
|
}
|
131
vendor/honnef.co/go/tools/cmd/unused/README.md
vendored
Normal file
131
vendor/honnef.co/go/tools/cmd/unused/README.md
vendored
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
# unused
|
||||||
|
|
||||||
|
_unused_ checks Go code for unused constants, variables, functions and
|
||||||
|
types.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
go get honnef.co/go/tools/cmd/unused
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
unused -help
|
||||||
|
|
||||||
|
## Usage Tips
|
||||||
|
|
||||||
|
- When running _unused_ on multiple packages, it will first try to
|
||||||
|
check them all at once, because that's faster. If any of the
|
||||||
|
packages don't compile, however, _unused_ will check each package
|
||||||
|
individually.
|
||||||
|
|
||||||
|
The first step can, depending on the number of packages, use a lot
|
||||||
|
of memory. For the entire standard library, it uses roughly 800 MB.
|
||||||
|
For a GOPATH with thousands of packages, it can quickly use several
|
||||||
|
gigabytes. If that is an issue, consider using something like this
|
||||||
|
instead:
|
||||||
|
|
||||||
|
```
|
||||||
|
for pkg in $(go list your_selection); do unused "$pkg"; done
|
||||||
|
```
|
||||||
|
|
||||||
|
This will effectively skip the first step and always check every
|
||||||
|
package individually.
|
||||||
|
|
||||||
|
## What counts as used/unused?
|
||||||
|
|
||||||
|
_unused_ checks for unused constants, functions, types and optionally
|
||||||
|
struct fields. They will be considered used or unused under the
|
||||||
|
following conditions:
|
||||||
|
|
||||||
|
- Unexported package-level objects will be reported as unused if there
|
||||||
|
are no explicit references to them.
|
||||||
|
|
||||||
|
- Unexported methods will be reported as unused if there are no
|
||||||
|
explicit references to them and if they don't implement any
|
||||||
|
interfaces.
|
||||||
|
|
||||||
|
- The `main` function is considered as used if it's in the `main`
|
||||||
|
package.
|
||||||
|
|
||||||
|
- `init` functions are always considered as used.
|
||||||
|
|
||||||
|
- Exported objects in function scope are treated like unexported
|
||||||
|
objects.
|
||||||
|
|
||||||
|
- Exported functions in tests are treated like unexported functions,
|
||||||
|
unless they're test, benchmark or example functions.
|
||||||
|
|
||||||
|
- Struct fields will be considered as unused if there are no explicit
|
||||||
|
references to them. Unkeyed composite literals with >=1 elements
|
||||||
|
mark all fields of the struct as used.
|
||||||
|
|
||||||
|
- Neither the checks for methods nor for struct fields are aware of
|
||||||
|
the reflect package and may thus produce false positives.
|
||||||
|
|
||||||
|
## Whole program analysis
|
||||||
|
|
||||||
|
Optionally via the `-exported` flag, _unused_ can analyse all
|
||||||
|
arguments as a single program and report unused exported identifiers.
|
||||||
|
This can be useful for checking "internal" packages, or large software
|
||||||
|
projects that do not export an API to the public, but use exported
|
||||||
|
methods between components.
|
||||||
|
|
||||||
|
Do note that in the whole-program analysis, all arguments must
|
||||||
|
type-check. It is not possible to check packages individually in this
|
||||||
|
mode.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
$ time unused cmd/go
|
||||||
|
/usr/lib/go/src/cmd/go/build.go:1327:6: func hasString is unused
|
||||||
|
/usr/lib/go/src/cmd/go/build.go:2328:6: func toolVerify is unused
|
||||||
|
/usr/lib/go/src/cmd/go/generate.go:375:21: func identLength is unused
|
||||||
|
/usr/lib/go/src/cmd/go/get.go:474:5: var goTag is unused
|
||||||
|
/usr/lib/go/src/cmd/go/get.go:513:6: func cmpGoVersion is unused
|
||||||
|
/usr/lib/go/src/cmd/go/go_test.go:426:23: func grepCountStdout is unused
|
||||||
|
/usr/lib/go/src/cmd/go/go_test.go:432:23: func grepCountStderr is unused
|
||||||
|
/usr/lib/go/src/cmd/go/main.go:406:5: var logf is unused
|
||||||
|
/usr/lib/go/src/cmd/go/main.go:431:6: func runOut is unused
|
||||||
|
/usr/lib/go/src/cmd/go/pkg.go:91:2: field forceBuild is unused
|
||||||
|
/usr/lib/go/src/cmd/go/pkg.go:688:2: const toRoot is unused
|
||||||
|
/usr/lib/go/src/cmd/go/testflag.go:278:6: func setIntFlag is unused
|
||||||
|
unused cmd/go 3.33s user 0.25s system 447% cpu 0.799 total
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ time unused $(go list github.com/prometheus/prometheus/... | grep -v /vendor/)
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/promql/engine_test.go:11:5: var noop is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/dns.go:39:2: const interval is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/dns.go:69:2: field m is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/nerve.go:31:2: const nerveNodePrefix is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/serverset.go:33:2: const serversetNodePrefix is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/scrape.go:41:2: const ingestedSamplesCap is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/scrape.go:49:2: var errSkippedScrape is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/targetmanager.go:184:2: field providers is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/delta.go:394:2: field error is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/delta.go:398:3: field error is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/doubledelta.go:500:2: field error is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/doubledelta.go:504:3: field error is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/remote/opentsdb/client.go:40:2: var illegalCharsRE is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/util/stats/timer.go:56:2: field child is unused
|
||||||
|
/home/dominikh/prj/src/github.com/prometheus/prometheus/util/treecache/treecache.go:25:2: field zkEvents is unused
|
||||||
|
unused $(go list github.com/prometheus/prometheus/... | grep -v /vendor/) 5.70s user 0.43s system 535% cpu 1.142 total
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ time unused -exported github.com/kr/pretty/...
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/formatter.go:14:2: const limit is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/formatter.go:322:6: func tryDeepEqual is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:20:6: func Errorf is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:28:6: func Fprintf is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:37:6: func Log is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:45:6: func Logf is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:54:6: func Logln is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:63:6: func Print is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:71:6: func Printf is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:80:6: func Println is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:88:6: func Sprintf is unused
|
||||||
|
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:92:6: func wrap is unused
|
||||||
|
unused -exported github.com/kr/pretty/... 1.23s user 0.19s system 253% cpu 0.558 total
|
||||||
|
```
|
78
vendor/honnef.co/go/tools/cmd/unused/main.go
vendored
Normal file
78
vendor/honnef.co/go/tools/cmd/unused/main.go
vendored
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// unused reports unused identifiers (types, functions, ...) in your
|
||||||
|
// code.
|
||||||
|
package main // import "honnef.co/go/tools/cmd/unused"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"honnef.co/go/tools/lint/lintutil"
|
||||||
|
"honnef.co/go/tools/unused"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fConstants bool
|
||||||
|
fFields bool
|
||||||
|
fFunctions bool
|
||||||
|
fTypes bool
|
||||||
|
fVariables bool
|
||||||
|
fDebug string
|
||||||
|
fWholeProgram bool
|
||||||
|
fReflection bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func newChecker(mode unused.CheckMode) *unused.Checker {
|
||||||
|
checker := unused.NewChecker(mode)
|
||||||
|
|
||||||
|
if fDebug != "" {
|
||||||
|
debug, err := os.Create(fDebug)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("couldn't open debug file:", err)
|
||||||
|
}
|
||||||
|
checker.Debug = debug
|
||||||
|
}
|
||||||
|
|
||||||
|
checker.WholeProgram = fWholeProgram
|
||||||
|
checker.ConsiderReflection = fReflection
|
||||||
|
return checker
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
|
||||||
|
fs := lintutil.FlagSet("unused")
|
||||||
|
fs.BoolVar(&fConstants, "consts", true, "Report unused constants")
|
||||||
|
fs.BoolVar(&fFields, "fields", true, "Report unused fields")
|
||||||
|
fs.BoolVar(&fFunctions, "funcs", true, "Report unused functions and methods")
|
||||||
|
fs.BoolVar(&fTypes, "types", true, "Report unused types")
|
||||||
|
fs.BoolVar(&fVariables, "vars", true, "Report unused variables")
|
||||||
|
fs.StringVar(&fDebug, "debug", "", "Write a debug graph to `file`. Existing files will be overwritten.")
|
||||||
|
fs.BoolVar(&fWholeProgram, "exported", false, "Treat arguments as a program and report unused exported identifiers")
|
||||||
|
fs.BoolVar(&fReflection, "reflect", true, "Consider identifiers as used when it's likely they'll be accessed via reflection")
|
||||||
|
fs.Parse(os.Args[1:])
|
||||||
|
|
||||||
|
var mode unused.CheckMode
|
||||||
|
if fConstants {
|
||||||
|
mode |= unused.CheckConstants
|
||||||
|
}
|
||||||
|
if fFields {
|
||||||
|
mode |= unused.CheckFields
|
||||||
|
}
|
||||||
|
if fFunctions {
|
||||||
|
mode |= unused.CheckFunctions
|
||||||
|
}
|
||||||
|
if fTypes {
|
||||||
|
mode |= unused.CheckTypes
|
||||||
|
}
|
||||||
|
if fVariables {
|
||||||
|
mode |= unused.CheckVariables
|
||||||
|
}
|
||||||
|
|
||||||
|
checker := newChecker(mode)
|
||||||
|
l := unused.NewLintChecker(checker)
|
||||||
|
cfg := lintutil.CheckerConfig{
|
||||||
|
Checker: l,
|
||||||
|
ExitNonZero: true,
|
||||||
|
}
|
||||||
|
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
|
||||||
|
}
|
68
vendor/honnef.co/go/tools/internal/sharedcheck/lint.go
vendored
Normal file
68
vendor/honnef.co/go/tools/internal/sharedcheck/lint.go
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package sharedcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/types"
|
||||||
|
|
||||||
|
"honnef.co/go/tools/lint"
|
||||||
|
. "honnef.co/go/tools/lint/lintdsl"
|
||||||
|
"honnef.co/go/tools/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckRangeStringRunes(j *lint.Job) {
|
||||||
|
for _, ssafn := range j.Program.InitialFunctions {
|
||||||
|
fn := func(node ast.Node) bool {
|
||||||
|
rng, ok := node.(*ast.RangeStmt)
|
||||||
|
if !ok || !IsBlank(rng.Key) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
v, _ := ssafn.ValueForExpr(rng.X)
|
||||||
|
|
||||||
|
// Check that we're converting from string to []rune
|
||||||
|
val, _ := v.(*ssa.Convert)
|
||||||
|
if val == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Tsrc, ok := val.X.Type().(*types.Basic)
|
||||||
|
if !ok || Tsrc.Kind() != types.String {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Tdst, ok := val.Type().(*types.Slice)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
TdstElem, ok := 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(FilterDebug(*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
|
||||||
|
// SSA 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
|
||||||
|
}
|
||||||
|
|
||||||
|
j.Errorf(rng, "should range over string, not []rune(string)")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Inspect(ssafn.Syntax(), fn)
|
||||||
|
}
|
||||||
|
}
|
28
vendor/honnef.co/go/tools/lint/LICENSE
vendored
Normal file
28
vendor/honnef.co/go/tools/lint/LICENSE
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright (c) 2013 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.
|
543
vendor/honnef.co/go/tools/lint/lint.go
vendored
Normal file
543
vendor/honnef.co/go/tools/lint/lint.go
vendored
Normal file
|
@ -0,0 +1,543 @@
|
||||||
|
// Copyright (c) 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 or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd.
|
||||||
|
|
||||||
|
// Package lint provides the foundation for tools like gosimple.
|
||||||
|
package lint // import "honnef.co/go/tools/lint"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
|
"honnef.co/go/tools/ssa"
|
||||||
|
"honnef.co/go/tools/ssa/ssautil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Job struct {
|
||||||
|
Program *Program
|
||||||
|
|
||||||
|
checker string
|
||||||
|
check string
|
||||||
|
problems []Problem
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ignore interface {
|
||||||
|
Match(p Problem) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type LineIgnore struct {
|
||||||
|
File string
|
||||||
|
Line int
|
||||||
|
Checks []string
|
||||||
|
matched bool
|
||||||
|
pos token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (li *LineIgnore) Match(p Problem) bool {
|
||||||
|
if p.Position.Filename != li.File || p.Position.Line != li.Line {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range li.Checks {
|
||||||
|
if m, _ := filepath.Match(c, p.Check); m {
|
||||||
|
li.matched = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (li *LineIgnore) String() string {
|
||||||
|
matched := "not matched"
|
||||||
|
if li.matched {
|
||||||
|
matched = "matched"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d %s (%s)", li.File, li.Line, strings.Join(li.Checks, ", "), matched)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileIgnore struct {
|
||||||
|
File string
|
||||||
|
Checks []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fi *FileIgnore) Match(p Problem) bool {
|
||||||
|
if p.Position.Filename != fi.File {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range fi.Checks {
|
||||||
|
if m, _ := filepath.Match(c, p.Check); m {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type GlobIgnore struct {
|
||||||
|
Pattern string
|
||||||
|
Checks []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *GlobIgnore) Match(p Problem) bool {
|
||||||
|
if gi.Pattern != "*" {
|
||||||
|
pkgpath := p.Package.Path()
|
||||||
|
if strings.HasSuffix(pkgpath, "_test") {
|
||||||
|
pkgpath = pkgpath[:len(pkgpath)-len("_test")]
|
||||||
|
}
|
||||||
|
name := filepath.Join(pkgpath, filepath.Base(p.Position.Filename))
|
||||||
|
if m, _ := filepath.Match(gi.Pattern, name); !m {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range gi.Checks {
|
||||||
|
if m, _ := filepath.Match(c, p.Check); m {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type Program struct {
|
||||||
|
SSA *ssa.Program
|
||||||
|
Prog *loader.Program
|
||||||
|
// TODO(dh): Rename to InitialPackages?
|
||||||
|
Packages []*Pkg
|
||||||
|
InitialFunctions []*ssa.Function
|
||||||
|
AllFunctions []*ssa.Function
|
||||||
|
Files []*ast.File
|
||||||
|
Info *types.Info
|
||||||
|
GoVersion int
|
||||||
|
|
||||||
|
tokenFileMap map[*token.File]*ast.File
|
||||||
|
astFileMap map[*ast.File]*Pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
type Func func(*Job)
|
||||||
|
|
||||||
|
// Problem represents a problem in some source code.
|
||||||
|
type Problem struct {
|
||||||
|
pos token.Pos
|
||||||
|
Position token.Position // position in source file
|
||||||
|
Text string // the prose that describes the problem
|
||||||
|
Check string
|
||||||
|
Checker string
|
||||||
|
Package *types.Package
|
||||||
|
Ignored bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Problem) String() string {
|
||||||
|
if p.Check == "" {
|
||||||
|
return p.Text
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s (%s)", p.Text, p.Check)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Checker interface {
|
||||||
|
Name() string
|
||||||
|
Prefix() string
|
||||||
|
Init(*Program)
|
||||||
|
Funcs() map[string]Func
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Linter lints Go source code.
|
||||||
|
type Linter struct {
|
||||||
|
Checker Checker
|
||||||
|
Ignores []Ignore
|
||||||
|
GoVersion int
|
||||||
|
ReturnIgnored bool
|
||||||
|
|
||||||
|
automaticIgnores []Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Linter) ignore(p Problem) bool {
|
||||||
|
ignored := false
|
||||||
|
for _, ig := range l.automaticIgnores {
|
||||||
|
// We cannot short-circuit these, as we want to record, for
|
||||||
|
// each ignore, whether it matched or not.
|
||||||
|
if ig.Match(p) {
|
||||||
|
ignored = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ignored {
|
||||||
|
// no need to execute other ignores if we've already had a
|
||||||
|
// match.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, ig := range l.Ignores {
|
||||||
|
// We can short-circuit here, as we aren't tracking any
|
||||||
|
// information.
|
||||||
|
if ig.Match(p) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prog *Program) File(node Positioner) *ast.File {
|
||||||
|
return prog.tokenFileMap[prog.SSA.Fset.File(node.Pos())]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Job) File(node Positioner) *ast.File {
|
||||||
|
return j.Program.File(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dh): switch to sort.Slice when Go 1.9 lands.
|
||||||
|
type byPosition struct {
|
||||||
|
fset *token.FileSet
|
||||||
|
ps []Problem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps byPosition) Len() int {
|
||||||
|
return len(ps.ps)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps byPosition) Less(i int, j int) bool {
|
||||||
|
pi, pj := ps.ps[i].Position, ps.ps[j].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
|
||||||
|
}
|
||||||
|
|
||||||
|
return ps.ps[i].Text < ps.ps[j].Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps byPosition) Swap(i int, j int) {
|
||||||
|
ps.ps[i], ps.ps[j] = ps.ps[j], ps.ps[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
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:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||||
|
ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
||||||
|
ssaprog.Build()
|
||||||
|
pkgMap := map[*ssa.Package]*Pkg{}
|
||||||
|
var pkgs []*Pkg
|
||||||
|
for _, pkginfo := range lprog.InitialPackages() {
|
||||||
|
ssapkg := ssaprog.Package(pkginfo.Pkg)
|
||||||
|
var bp *build.Package
|
||||||
|
if len(pkginfo.Files) != 0 {
|
||||||
|
path := lprog.Fset.Position(pkginfo.Files[0].Pos()).Filename
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
var err error
|
||||||
|
ctx := conf.Build
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = &build.Default
|
||||||
|
}
|
||||||
|
bp, err = ctx.ImportDir(dir, 0)
|
||||||
|
if err != nil {
|
||||||
|
// shouldn't happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pkg := &Pkg{
|
||||||
|
Package: ssapkg,
|
||||||
|
Info: pkginfo,
|
||||||
|
BuildPkg: bp,
|
||||||
|
}
|
||||||
|
pkgMap[ssapkg] = pkg
|
||||||
|
pkgs = append(pkgs, pkg)
|
||||||
|
}
|
||||||
|
prog := &Program{
|
||||||
|
SSA: ssaprog,
|
||||||
|
Prog: lprog,
|
||||||
|
Packages: pkgs,
|
||||||
|
Info: &types.Info{},
|
||||||
|
GoVersion: l.GoVersion,
|
||||||
|
tokenFileMap: map[*token.File]*ast.File{},
|
||||||
|
astFileMap: map[*ast.File]*Pkg{},
|
||||||
|
}
|
||||||
|
|
||||||
|
initial := map[*types.Package]struct{}{}
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
initial[pkg.Info.Pkg] = struct{}{}
|
||||||
|
}
|
||||||
|
for fn := range ssautil.AllFunctions(ssaprog) {
|
||||||
|
if fn.Pkg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prog.AllFunctions = append(prog.AllFunctions, fn)
|
||||||
|
if _, ok := initial[fn.Pkg.Pkg]; ok {
|
||||||
|
prog.InitialFunctions = append(prog.InitialFunctions, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
prog.Files = append(prog.Files, pkg.Info.Files...)
|
||||||
|
|
||||||
|
ssapkg := ssaprog.Package(pkg.Info.Pkg)
|
||||||
|
for _, f := range pkg.Info.Files {
|
||||||
|
prog.astFileMap[f] = pkgMap[ssapkg]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkginfo := range lprog.AllPackages {
|
||||||
|
for _, f := range pkginfo.Files {
|
||||||
|
tf := lprog.Fset.File(f.Pos())
|
||||||
|
prog.tokenFileMap[tf] = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []Problem
|
||||||
|
l.automaticIgnores = nil
|
||||||
|
for _, pkginfo := range lprog.InitialPackages() {
|
||||||
|
for _, f := range pkginfo.Files {
|
||||||
|
cm := ast.NewCommentMap(lprog.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)
|
||||||
|
switch cmd {
|
||||||
|
case "ignore", "file-ignore":
|
||||||
|
if len(args) < 2 {
|
||||||
|
// FIXME(dh): this causes duplicated warnings when using megacheck
|
||||||
|
p := Problem{
|
||||||
|
pos: c.Pos(),
|
||||||
|
Position: prog.DisplayPosition(c.Pos()),
|
||||||
|
Text: "malformed linter directive; missing the required reason field?",
|
||||||
|
Check: "",
|
||||||
|
Checker: l.Checker.Name(),
|
||||||
|
Package: nil,
|
||||||
|
}
|
||||||
|
out = append(out, p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// unknown directive, ignore
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
checks := strings.Split(args[0], ",")
|
||||||
|
pos := prog.DisplayPosition(node.Pos())
|
||||||
|
var ig Ignore
|
||||||
|
switch cmd {
|
||||||
|
case "ignore":
|
||||||
|
ig = &LineIgnore{
|
||||||
|
File: pos.Filename,
|
||||||
|
Line: pos.Line,
|
||||||
|
Checks: checks,
|
||||||
|
pos: c.Pos(),
|
||||||
|
}
|
||||||
|
case "file-ignore":
|
||||||
|
ig = &FileIgnore{
|
||||||
|
File: pos.Filename,
|
||||||
|
Checks: checks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.automaticIgnores = append(l.automaticIgnores, ig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes := struct {
|
||||||
|
types int
|
||||||
|
defs int
|
||||||
|
uses int
|
||||||
|
implicits int
|
||||||
|
selections int
|
||||||
|
scopes int
|
||||||
|
}{}
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
sizes.types += len(pkg.Info.Info.Types)
|
||||||
|
sizes.defs += len(pkg.Info.Info.Defs)
|
||||||
|
sizes.uses += len(pkg.Info.Info.Uses)
|
||||||
|
sizes.implicits += len(pkg.Info.Info.Implicits)
|
||||||
|
sizes.selections += len(pkg.Info.Info.Selections)
|
||||||
|
sizes.scopes += len(pkg.Info.Info.Scopes)
|
||||||
|
}
|
||||||
|
prog.Info.Types = make(map[ast.Expr]types.TypeAndValue, sizes.types)
|
||||||
|
prog.Info.Defs = make(map[*ast.Ident]types.Object, sizes.defs)
|
||||||
|
prog.Info.Uses = make(map[*ast.Ident]types.Object, sizes.uses)
|
||||||
|
prog.Info.Implicits = make(map[ast.Node]types.Object, sizes.implicits)
|
||||||
|
prog.Info.Selections = make(map[*ast.SelectorExpr]*types.Selection, sizes.selections)
|
||||||
|
prog.Info.Scopes = make(map[ast.Node]*types.Scope, sizes.scopes)
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
for k, v := range pkg.Info.Info.Types {
|
||||||
|
prog.Info.Types[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range pkg.Info.Info.Defs {
|
||||||
|
prog.Info.Defs[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range pkg.Info.Info.Uses {
|
||||||
|
prog.Info.Uses[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range pkg.Info.Info.Implicits {
|
||||||
|
prog.Info.Implicits[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range pkg.Info.Info.Selections {
|
||||||
|
prog.Info.Selections[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range pkg.Info.Info.Scopes {
|
||||||
|
prog.Info.Scopes[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.Checker.Init(prog)
|
||||||
|
|
||||||
|
funcs := l.Checker.Funcs()
|
||||||
|
var keys []string
|
||||||
|
for k := range funcs {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
var jobs []*Job
|
||||||
|
for _, k := range keys {
|
||||||
|
j := &Job{
|
||||||
|
Program: prog,
|
||||||
|
checker: l.Checker.Name(),
|
||||||
|
check: k,
|
||||||
|
}
|
||||||
|
jobs = append(jobs, j)
|
||||||
|
}
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
for _, j := range jobs {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(j *Job) {
|
||||||
|
defer wg.Done()
|
||||||
|
fn := funcs[j.check]
|
||||||
|
if fn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn(j)
|
||||||
|
}(j)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
for _, j := range jobs {
|
||||||
|
for _, p := range j.problems {
|
||||||
|
p.Ignored = l.ignore(p)
|
||||||
|
if l.ReturnIgnored || !p.Ignored {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ig := range l.automaticIgnores {
|
||||||
|
ig, ok := ig.(*LineIgnore)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ig.matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, c := range ig.Checks {
|
||||||
|
idx := strings.IndexFunc(c, func(r rune) bool {
|
||||||
|
return unicode.IsNumber(r)
|
||||||
|
})
|
||||||
|
if idx == -1 {
|
||||||
|
// malformed check name, backing out
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c[:idx] != l.Checker.Prefix() {
|
||||||
|
// not for this checker
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := Problem{
|
||||||
|
pos: ig.pos,
|
||||||
|
Position: prog.DisplayPosition(ig.pos),
|
||||||
|
Text: "this linter directive didn't match anything; should it be removed?",
|
||||||
|
Check: "",
|
||||||
|
Checker: l.Checker.Name(),
|
||||||
|
Package: nil,
|
||||||
|
}
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(byPosition{lprog.Fset, out})
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pkg represents a package being linted.
|
||||||
|
type Pkg struct {
|
||||||
|
*ssa.Package
|
||||||
|
Info *loader.PackageInfo
|
||||||
|
BuildPkg *build.Package
|
||||||
|
}
|
||||||
|
|
||||||
|
type Positioner interface {
|
||||||
|
Pos() token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prog *Program) DisplayPosition(p token.Pos) token.Position {
|
||||||
|
// The //line compiler directive can be used to change the file
|
||||||
|
// name and line numbers associated with code. This can, for
|
||||||
|
// example, be used by code generation tools. The most prominent
|
||||||
|
// example is 'go tool cgo', which uses //line directives to refer
|
||||||
|
// back to the original source code.
|
||||||
|
//
|
||||||
|
// In the context of our linters, we need to treat these
|
||||||
|
// directives differently depending on context. For cgo files, we
|
||||||
|
// want to honour the directives, so that line numbers are
|
||||||
|
// adjusted correctly. For all other files, we want to ignore the
|
||||||
|
// directives, so that problems are reported at their actual
|
||||||
|
// position and not, for example, a yacc grammar file. This also
|
||||||
|
// affects the ignore mechanism, since it operates on the position
|
||||||
|
// information stored within problems. With this implementation, a
|
||||||
|
// user will ignore foo.go, not foo.y
|
||||||
|
|
||||||
|
pkg := prog.astFileMap[prog.tokenFileMap[prog.Prog.Fset.File(p)]]
|
||||||
|
bp := pkg.BuildPkg
|
||||||
|
adjPos := prog.Prog.Fset.Position(p)
|
||||||
|
if bp == nil {
|
||||||
|
// couldn't find the package for some reason (deleted? faulty
|
||||||
|
// file system?)
|
||||||
|
return adjPos
|
||||||
|
}
|
||||||
|
base := filepath.Base(adjPos.Filename)
|
||||||
|
for _, f := range bp.CgoFiles {
|
||||||
|
if f == base {
|
||||||
|
// this is a cgo file, use the adjusted position
|
||||||
|
return adjPos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// not a cgo file, ignore //line directives
|
||||||
|
return prog.Prog.Fset.PositionFor(p, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem {
|
||||||
|
tf := j.Program.SSA.Fset.File(n.Pos())
|
||||||
|
f := j.Program.tokenFileMap[tf]
|
||||||
|
pkg := j.Program.astFileMap[f].Pkg
|
||||||
|
|
||||||
|
pos := j.Program.DisplayPosition(n.Pos())
|
||||||
|
problem := Problem{
|
||||||
|
pos: n.Pos(),
|
||||||
|
Position: pos,
|
||||||
|
Text: fmt.Sprintf(format, args...),
|
||||||
|
Check: j.check,
|
||||||
|
Checker: j.checker,
|
||||||
|
Package: pkg,
|
||||||
|
}
|
||||||
|
j.problems = append(j.problems, problem)
|
||||||
|
return &j.problems[len(j.problems)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Job) NodePackage(node Positioner) *Pkg {
|
||||||
|
f := j.File(node)
|
||||||
|
return j.Program.astFileMap[f]
|
||||||
|
}
|
282
vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go
vendored
Normal file
282
vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go
vendored
Normal file
|
@ -0,0 +1,282 @@
|
||||||
|
// Package lintdsl provides helpers for implementing static analysis
|
||||||
|
// checks. Dot-importing this package is encouraged.
|
||||||
|
package lintdsl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/constant"
|
||||||
|
"go/printer"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"honnef.co/go/tools/lint"
|
||||||
|
"honnef.co/go/tools/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
type packager interface {
|
||||||
|
Package() *ssa.Package
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallName(call *ssa.CallCommon) string {
|
||||||
|
if call.IsInvoke() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch v := call.Value.(type) {
|
||||||
|
case *ssa.Function:
|
||||||
|
fn, ok := v.Object().(*types.Func)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fn.FullName()
|
||||||
|
case *ssa.Builtin:
|
||||||
|
return v.Name()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsCallTo(call *ssa.CallCommon, name string) bool { return CallName(call) == name }
|
||||||
|
func IsType(T types.Type, name string) bool { return types.TypeString(T, nil) == name }
|
||||||
|
|
||||||
|
func FilterDebug(instr []ssa.Instruction) []ssa.Instruction {
|
||||||
|
var out []ssa.Instruction
|
||||||
|
for _, ins := range instr {
|
||||||
|
if _, ok := ins.(*ssa.DebugRef); !ok {
|
||||||
|
out = append(out, ins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsExample(fn *ssa.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")
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsPointerLike(T types.Type) bool {
|
||||||
|
switch T := T.Underlying().(type) {
|
||||||
|
case *types.Interface, *types.Chan, *types.Map, *types.Pointer:
|
||||||
|
return true
|
||||||
|
case *types.Basic:
|
||||||
|
return T.Kind() == types.UnsafePointer
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsGenerated(f *ast.File) bool {
|
||||||
|
comments := f.Comments
|
||||||
|
if len(comments) > 0 {
|
||||||
|
comment := comments[0].Text()
|
||||||
|
return strings.Contains(comment, "Code generated by") ||
|
||||||
|
strings.Contains(comment, "DO NOT EDIT")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == "_"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 TypeOf(j *lint.Job, expr ast.Expr) types.Type { return j.Program.Info.TypeOf(expr) }
|
||||||
|
func IsOfType(j *lint.Job, expr ast.Expr, name string) bool { return IsType(TypeOf(j, expr), name) }
|
||||||
|
|
||||||
|
func ObjectOf(j *lint.Job, ident *ast.Ident) types.Object { return j.Program.Info.ObjectOf(ident) }
|
||||||
|
|
||||||
|
func IsInTest(j *lint.Job, node lint.Positioner) bool {
|
||||||
|
// FIXME(dh): this doesn't work for global variables with
|
||||||
|
// initializers
|
||||||
|
f := j.Program.SSA.Fset.File(node.Pos())
|
||||||
|
return f != nil && strings.HasSuffix(f.Name(), "_test.go")
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsInMain(j *lint.Job, node lint.Positioner) bool {
|
||||||
|
if node, ok := node.(packager); ok {
|
||||||
|
return node.Package().Pkg.Name() == "main"
|
||||||
|
}
|
||||||
|
pkg := j.NodePackage(node)
|
||||||
|
if pkg == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return pkg.Pkg.Name() == "main"
|
||||||
|
}
|
||||||
|
|
||||||
|
func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
|
||||||
|
sel := j.Program.Info.Selections[expr]
|
||||||
|
if sel == nil {
|
||||||
|
if x, ok := expr.X.(*ast.Ident); ok {
|
||||||
|
pkg, ok := j.Program.Info.ObjectOf(x).(*types.PkgName)
|
||||||
|
if !ok {
|
||||||
|
// This shouldn't happen
|
||||||
|
return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("unsupported selector: %v", expr))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNil(j *lint.Job, expr ast.Expr) bool {
|
||||||
|
return j.Program.Info.Types[expr].IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoolConst(j *lint.Job, expr ast.Expr) bool {
|
||||||
|
val := j.Program.Info.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
|
||||||
|
return constant.BoolVal(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsBoolConst(j *lint.Job, 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.
|
||||||
|
|
||||||
|
ident, ok := expr.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
obj := j.Program.Info.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(j *lint.Job, expr ast.Expr) (int64, bool) {
|
||||||
|
tv := j.Program.Info.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(j *lint.Job, expr ast.Expr) (string, bool) {
|
||||||
|
val := j.Program.Info.Types[expr].Value
|
||||||
|
if val == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if val.Kind() != constant.String {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return constant.StringVal(val), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 IsGoVersion(j *lint.Job, minor int) bool {
|
||||||
|
return j.Program.GoVersion >= minor
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsCallToAST(j *lint.Job, node ast.Node, name string) bool {
|
||||||
|
call, ok := node.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sel, ok := call.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fn, ok := j.Program.Info.ObjectOf(sel.Sel).(*types.Func)
|
||||||
|
return ok && fn.FullName() == name
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsCallToAnyAST(j *lint.Job, node ast.Node, names ...string) bool {
|
||||||
|
for _, name := range names {
|
||||||
|
if IsCallToAST(j, node, name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func Render(j *lint.Job, x interface{}) string {
|
||||||
|
fset := j.Program.SSA.Fset
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := printer.Fprint(&buf, fset, x); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderArgs(j *lint.Job, args []ast.Expr) string {
|
||||||
|
var ss []string
|
||||||
|
for _, arg := range args {
|
||||||
|
ss = append(ss, Render(j, arg))
|
||||||
|
}
|
||||||
|
return strings.Join(ss, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Inspect(node ast.Node, fn func(node ast.Node) bool) {
|
||||||
|
if node == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ast.Inspect(node, fn)
|
||||||
|
}
|
350
vendor/honnef.co/go/tools/lint/lintutil/util.go
vendored
Normal file
350
vendor/honnef.co/go/tools/lint/lintutil/util.go
vendored
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
// Copyright (c) 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 or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd.
|
||||||
|
|
||||||
|
// Package lintutil provides helpers for writing linter command lines.
|
||||||
|
package lintutil // import "honnef.co/go/tools/lint/lintutil"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"honnef.co/go/tools/lint"
|
||||||
|
"honnef.co/go/tools/version"
|
||||||
|
|
||||||
|
"github.com/kisielk/gotool"
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OutputFormatter interface {
|
||||||
|
Format(p lint.Problem)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextOutput struct {
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o TextOutput) Format(p lint.Problem) {
|
||||||
|
fmt.Fprintf(o.w, "%v: %s\n", relativePositionString(p.Position), p.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONOutput struct {
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o JSONOutput) Format(p lint.Problem) {
|
||||||
|
type location struct {
|
||||||
|
File string `json:"file"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
Column int `json:"column"`
|
||||||
|
}
|
||||||
|
jp := struct {
|
||||||
|
Checker string `json:"checker"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Severity string `json:"severity,omitempty"`
|
||||||
|
Location location `json:"location"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Ignored bool `json:"ignored"`
|
||||||
|
}{
|
||||||
|
p.Checker,
|
||||||
|
p.Check,
|
||||||
|
"", // TODO(dh): support severity
|
||||||
|
location{
|
||||||
|
p.Position.Filename,
|
||||||
|
p.Position.Line,
|
||||||
|
p.Position.Column,
|
||||||
|
},
|
||||||
|
p.Text,
|
||||||
|
p.Ignored,
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(o.w).Encode(jp)
|
||||||
|
}
|
||||||
|
func usage(name string, flags *flag.FlagSet) func() {
|
||||||
|
return func() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
|
||||||
|
fmt.Fprintf(os.Stderr, "\t%s [flags] # runs on package in current directory\n", name)
|
||||||
|
fmt.Fprintf(os.Stderr, "\t%s [flags] packages\n", name)
|
||||||
|
fmt.Fprintf(os.Stderr, "\t%s [flags] directory\n", name)
|
||||||
|
fmt.Fprintf(os.Stderr, "\t%s [flags] files... # must be a single package\n", name)
|
||||||
|
fmt.Fprintf(os.Stderr, "Flags:\n")
|
||||||
|
flags.PrintDefaults()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type runner struct {
|
||||||
|
checker lint.Checker
|
||||||
|
tags []string
|
||||||
|
ignores []lint.Ignore
|
||||||
|
version int
|
||||||
|
returnIgnored bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveRelative(importPaths []string, tags []string) (goFiles bool, err error) {
|
||||||
|
if len(importPaths) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(importPaths[0], ".go") {
|
||||||
|
// User is specifying a package in terms of .go files, don't resolve
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
ctx := build.Default
|
||||||
|
ctx.BuildTags = tags
|
||||||
|
for i, path := range importPaths {
|
||||||
|
bpkg, err := ctx.Import(path, wd, build.FindOnly)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("can't load package %q: %v", path, err)
|
||||||
|
}
|
||||||
|
importPaths[i] = bpkg.ImportPath
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIgnore(s string) ([]lint.Ignore, error) {
|
||||||
|
var out []lint.Ignore
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
for _, part := range strings.Fields(s) {
|
||||||
|
p := strings.Split(part, ":")
|
||||||
|
if len(p) != 2 {
|
||||||
|
return nil, errors.New("malformed ignore string")
|
||||||
|
}
|
||||||
|
path := p[0]
|
||||||
|
checks := strings.Split(p[1], ",")
|
||||||
|
out = append(out, &lint.GlobIgnore{Pattern: path, Checks: checks})
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type versionFlag int
|
||||||
|
|
||||||
|
func (v *versionFlag) String() string {
|
||||||
|
return fmt.Sprintf("1.%d", *v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *versionFlag) Set(s string) error {
|
||||||
|
if len(s) < 3 {
|
||||||
|
return errors.New("invalid Go version")
|
||||||
|
}
|
||||||
|
if s[0] != '1' {
|
||||||
|
return errors.New("invalid Go version")
|
||||||
|
}
|
||||||
|
if s[1] != '.' {
|
||||||
|
return errors.New("invalid Go version")
|
||||||
|
}
|
||||||
|
i, err := strconv.Atoi(s[2:])
|
||||||
|
*v = versionFlag(i)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *versionFlag) Get() interface{} {
|
||||||
|
return int(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FlagSet(name string) *flag.FlagSet {
|
||||||
|
flags := flag.NewFlagSet("", flag.ExitOnError)
|
||||||
|
flags.Usage = usage(name, flags)
|
||||||
|
flags.Float64("min_confidence", 0, "Deprecated; use -ignore instead")
|
||||||
|
flags.String("tags", "", "List of `build tags`")
|
||||||
|
flags.String("ignore", "", "Space separated list of checks to ignore, in the following format: 'import/path/file.go:Check1,Check2,...' Both the import path and file name sections support globbing, e.g. 'os/exec/*_test.go'")
|
||||||
|
flags.Bool("tests", true, "Include tests")
|
||||||
|
flags.Bool("version", false, "Print version and exit")
|
||||||
|
flags.Bool("show-ignored", false, "Don't filter ignored problems")
|
||||||
|
flags.String("f", "text", "Output `format` (valid choices are 'text' and 'json')")
|
||||||
|
|
||||||
|
tags := build.Default.ReleaseTags
|
||||||
|
v := tags[len(tags)-1][2:]
|
||||||
|
version := new(versionFlag)
|
||||||
|
if err := version.Set(v); err != nil {
|
||||||
|
panic(fmt.Sprintf("internal error: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
flags.Var(version, "go", "Target Go `version` in the format '1.x'")
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckerConfig struct {
|
||||||
|
Checker lint.Checker
|
||||||
|
ExitNonZero bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessFlagSet(confs []CheckerConfig, fs *flag.FlagSet) {
|
||||||
|
tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
|
||||||
|
ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string)
|
||||||
|
tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
|
||||||
|
goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
|
||||||
|
format := fs.Lookup("f").Value.(flag.Getter).Get().(string)
|
||||||
|
printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
|
||||||
|
showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
|
||||||
|
|
||||||
|
if printVersion {
|
||||||
|
version.Print()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs []lint.Checker
|
||||||
|
for _, conf := range confs {
|
||||||
|
cs = append(cs, conf.Checker)
|
||||||
|
}
|
||||||
|
pss, err := Lint(cs, fs.Args(), &Options{
|
||||||
|
Tags: strings.Fields(tags),
|
||||||
|
LintTests: tests,
|
||||||
|
Ignores: ignore,
|
||||||
|
GoVersion: goVersion,
|
||||||
|
ReturnIgnored: showIgnored,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ps []lint.Problem
|
||||||
|
for _, p := range pss {
|
||||||
|
ps = append(ps, p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var f OutputFormatter
|
||||||
|
switch format {
|
||||||
|
case "text":
|
||||||
|
f = TextOutput{os.Stdout}
|
||||||
|
case "json":
|
||||||
|
f = JSONOutput{os.Stdout}
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", format)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range ps {
|
||||||
|
f.Format(p)
|
||||||
|
}
|
||||||
|
for i, p := range pss {
|
||||||
|
if len(p) != 0 && confs[i].ExitNonZero {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Tags []string
|
||||||
|
LintTests bool
|
||||||
|
Ignores string
|
||||||
|
GoVersion int
|
||||||
|
ReturnIgnored bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lint(cs []lint.Checker, pkgs []string, opt *Options) ([][]lint.Problem, error) {
|
||||||
|
if opt == nil {
|
||||||
|
opt = &Options{}
|
||||||
|
}
|
||||||
|
ignores, err := parseIgnore(opt.Ignores)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
paths := gotool.ImportPaths(pkgs)
|
||||||
|
goFiles, err := resolveRelative(paths, opt.Tags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctx := build.Default
|
||||||
|
ctx.BuildTags = opt.Tags
|
||||||
|
hadError := false
|
||||||
|
conf := &loader.Config{
|
||||||
|
Build: &ctx,
|
||||||
|
ParserMode: parser.ParseComments,
|
||||||
|
ImportPkgs: map[string]bool{},
|
||||||
|
TypeChecker: types.Config{
|
||||||
|
Sizes: types.SizesFor(ctx.Compiler, ctx.GOARCH),
|
||||||
|
Error: func(err error) {
|
||||||
|
// Only print the first error found
|
||||||
|
if hadError {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hadError = true
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if goFiles {
|
||||||
|
conf.CreateFromFilenames("adhoc", paths...)
|
||||||
|
} else {
|
||||||
|
for _, path := range paths {
|
||||||
|
conf.ImportPkgs[path] = opt.LintTests
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lprog, err := conf.Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var problems [][]lint.Problem
|
||||||
|
for _, c := range cs {
|
||||||
|
runner := &runner{
|
||||||
|
checker: c,
|
||||||
|
tags: opt.Tags,
|
||||||
|
ignores: ignores,
|
||||||
|
version: opt.GoVersion,
|
||||||
|
returnIgnored: opt.ReturnIgnored,
|
||||||
|
}
|
||||||
|
problems = append(problems, runner.lint(lprog, conf))
|
||||||
|
}
|
||||||
|
return problems, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessArgs(name string, cs []CheckerConfig, args []string) {
|
||||||
|
flags := FlagSet(name)
|
||||||
|
flags.Parse(args)
|
||||||
|
|
||||||
|
ProcessFlagSet(cs, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (runner *runner) lint(lprog *loader.Program, conf *loader.Config) []lint.Problem {
|
||||||
|
l := &lint.Linter{
|
||||||
|
Checker: runner.checker,
|
||||||
|
Ignores: runner.ignores,
|
||||||
|
GoVersion: runner.version,
|
||||||
|
ReturnIgnored: runner.returnIgnored,
|
||||||
|
}
|
||||||
|
return l.Lint(lprog, conf)
|
||||||
|
}
|
15
vendor/honnef.co/go/tools/simple/CONTRIBUTING.md
vendored
Normal file
15
vendor/honnef.co/go/tools/simple/CONTRIBUTING.md
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Contributing to gosimple
|
||||||
|
|
||||||
|
## Before filing an issue:
|
||||||
|
|
||||||
|
### Are you having trouble building gosimple?
|
||||||
|
|
||||||
|
Check you have the latest version of its dependencies. Run
|
||||||
|
```
|
||||||
|
go get -u honnef.co/go/tools/simple
|
||||||
|
```
|
||||||
|
If you still have problems, consider searching for existing issues before filing a new issue.
|
||||||
|
|
||||||
|
## Before sending a pull request:
|
||||||
|
|
||||||
|
Have you understood the purpose of gosimple? Make sure to carefully read `README`.
|
1739
vendor/honnef.co/go/tools/simple/lint.go
vendored
Normal file
1739
vendor/honnef.co/go/tools/simple/lint.go
vendored
Normal file
File diff suppressed because it is too large
Load diff
7
vendor/honnef.co/go/tools/simple/lint17.go
vendored
Normal file
7
vendor/honnef.co/go/tools/simple/lint17.go
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package simple
|
||||||
|
|
||||||
|
import "go/types"
|
||||||
|
|
||||||
|
var structsIdentical = types.Identical
|
7
vendor/honnef.co/go/tools/simple/lint18.go
vendored
Normal file
7
vendor/honnef.co/go/tools/simple/lint18.go
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package simple
|
||||||
|
|
||||||
|
import "go/types"
|
||||||
|
|
||||||
|
var structsIdentical = types.IdenticalIgnoreTags
|
28
vendor/honnef.co/go/tools/ssa/LICENSE
vendored
Normal file
28
vendor/honnef.co/go/tools/ssa/LICENSE
vendored
Normal file
|
@ -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.
|
195
vendor/honnef.co/go/tools/ssa/blockopt.go
vendored
Normal file
195
vendor/honnef.co/go/tools/ssa/blockopt.go
vendored
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// 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.Index = -1
|
||||||
|
for _, succ := range b.Succs {
|
||||||
|
if succ.Index == 0 {
|
||||||
|
markReachable(succ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteUnreachableBlocks(f *Function) {
|
||||||
|
deleteUnreachableBlocks(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.Index temporarily as the mark bit.
|
||||||
|
for _, b := range f.Blocks {
|
||||||
|
b.Index = white
|
||||||
|
}
|
||||||
|
markReachable(f.Blocks[0])
|
||||||
|
if f.Recover != nil {
|
||||||
|
markReachable(f.Recover)
|
||||||
|
}
|
||||||
|
for i, b := range f.Blocks {
|
||||||
|
if b.Index == white {
|
||||||
|
for _, c := range b.Succs {
|
||||||
|
if c.Index == 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 b.Instrs == nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func OptimizeBlocks(f *Function) {
|
||||||
|
optimizeBlocks(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
2379
vendor/honnef.co/go/tools/ssa/builder.go
vendored
Normal file
2379
vendor/honnef.co/go/tools/ssa/builder.go
vendored
Normal file
File diff suppressed because it is too large
Load diff
169
vendor/honnef.co/go/tools/ssa/const.go
vendored
Normal file
169
vendor/honnef.co/go/tools/ssa/const.go
vendored
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// This file defines the Const SSA value type.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
exact "go/constant"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 exact.Value, typ types.Type) *Const {
|
||||||
|
return &Const{typ, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) *Const {
|
||||||
|
return NewConst(exact.MakeInt64(i), tInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nilConst returns a nil constant of the specified type, which may
|
||||||
|
// be any reference type, including interfaces.
|
||||||
|
//
|
||||||
|
func nilConst(typ types.Type) *Const {
|
||||||
|
return NewConst(nil, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringConst returns a 'string' constant that evaluates to s.
|
||||||
|
func stringConst(s string) *Const {
|
||||||
|
return NewConst(exact.MakeString(s), tString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// zeroConst returns a new "zero" constant of the specified type,
|
||||||
|
// which must not be an array or struct type: the zero values of
|
||||||
|
// aggregates are well-defined but cannot be represented by Const.
|
||||||
|
//
|
||||||
|
func zeroConst(t types.Type) *Const {
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.Basic:
|
||||||
|
switch {
|
||||||
|
case t.Info()&types.IsBoolean != 0:
|
||||||
|
return NewConst(exact.MakeBool(false), t)
|
||||||
|
case t.Info()&types.IsNumeric != 0:
|
||||||
|
return NewConst(exact.MakeInt64(0), t)
|
||||||
|
case t.Info()&types.IsString != 0:
|
||||||
|
return NewConst(exact.MakeString(""), t)
|
||||||
|
case t.Kind() == types.UnsafePointer:
|
||||||
|
fallthrough
|
||||||
|
case t.Kind() == types.UntypedNil:
|
||||||
|
return nilConst(t)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprint("zeroConst for unexpected type:", t))
|
||||||
|
}
|
||||||
|
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
|
||||||
|
return nilConst(t)
|
||||||
|
case *types.Named:
|
||||||
|
return NewConst(zeroConst(t.Underlying()).Value, t)
|
||||||
|
case *types.Array, *types.Struct, *types.Tuple:
|
||||||
|
panic(fmt.Sprint("zeroConst applied to aggregate:", t))
|
||||||
|
}
|
||||||
|
panic(fmt.Sprint("zeroConst: unexpected ", t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Const) RelString(from *types.Package) string {
|
||||||
|
var s string
|
||||||
|
if c.Value == nil {
|
||||||
|
s = "nil"
|
||||||
|
} else if c.Value.Kind() == exact.String {
|
||||||
|
s = exact.StringVal(c.Value)
|
||||||
|
const max = 20
|
||||||
|
// TODO(adonovan): don't cut a rune in half.
|
||||||
|
if len(s) > max {
|
||||||
|
s = s[:max-3] + "..." // abbreviate
|
||||||
|
}
|
||||||
|
s = strconv.Quote(s)
|
||||||
|
} else {
|
||||||
|
s = c.Value.String()
|
||||||
|
}
|
||||||
|
return s + ":" + relType(c.Type(), from)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Const) Name() string {
|
||||||
|
return c.RelString(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Const) String() string {
|
||||||
|
return c.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Const) Type() types.Type {
|
||||||
|
return c.typ
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Const) Referrers() *[]Instruction {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Const) Parent() *Function { return nil }
|
||||||
|
|
||||||
|
func (c *Const) Pos() token.Pos {
|
||||||
|
return token.NoPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil returns true if this constant represents a typed or untyped nil value.
|
||||||
|
func (c *Const) IsNil() bool {
|
||||||
|
return c.Value == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(adonovan): move everything below into honnef.co/go/tools/ssa/interp.
|
||||||
|
|
||||||
|
// Int64 returns the numeric value of this constant truncated to fit
|
||||||
|
// a signed 64-bit integer.
|
||||||
|
//
|
||||||
|
func (c *Const) Int64() int64 {
|
||||||
|
switch x := exact.ToInt(c.Value); x.Kind() {
|
||||||
|
case exact.Int:
|
||||||
|
if i, ok := exact.Int64Val(x); ok {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
case exact.Float:
|
||||||
|
f, _ := exact.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 := exact.ToInt(c.Value); x.Kind() {
|
||||||
|
case exact.Int:
|
||||||
|
if u, ok := exact.Uint64Val(x); ok {
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
case exact.Float:
|
||||||
|
f, _ := exact.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 {
|
||||||
|
f, _ := exact.Float64Val(c.Value)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complex128 returns the complex value of this constant truncated to
|
||||||
|
// fit a complex128.
|
||||||
|
//
|
||||||
|
func (c *Const) Complex128() complex128 {
|
||||||
|
re, _ := exact.Float64Val(exact.Real(c.Value))
|
||||||
|
im, _ := exact.Float64Val(exact.Imag(c.Value))
|
||||||
|
return complex(re, im)
|
||||||
|
}
|
263
vendor/honnef.co/go/tools/ssa/create.go
vendored
Normal file
263
vendor/honnef.co/go/tools/ssa/create.go
vendored
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// This file implements the CREATE phase of SSA construction.
|
||||||
|
// See builder.go for explanation.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/types/typeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewProgram returns a new SSA Program.
|
||||||
|
//
|
||||||
|
// mode controls diagnostics and checking during SSA construction.
|
||||||
|
//
|
||||||
|
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
||||||
|
prog := &Program{
|
||||||
|
Fset: fset,
|
||||||
|
imported: make(map[string]*Package),
|
||||||
|
packages: make(map[*types.Package]*Package),
|
||||||
|
thunks: make(map[selectionKey]*Function),
|
||||||
|
bounds: make(map[*types.Func]*Function),
|
||||||
|
mode: mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
h := typeutil.MakeHasher() // protected by methodsMu, in effect
|
||||||
|
prog.methodSets.SetHasher(h)
|
||||||
|
prog.canon.SetHasher(h)
|
||||||
|
|
||||||
|
return prog
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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); it will be used during the build
|
||||||
|
// phase.
|
||||||
|
//
|
||||||
|
func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
|
||||||
|
name := obj.Name()
|
||||||
|
switch obj := obj.(type) {
|
||||||
|
case *types.Builtin:
|
||||||
|
if pkg.Pkg != types.Unsafe {
|
||||||
|
panic("unexpected builtin object: " + obj.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
case *types.TypeName:
|
||||||
|
pkg.Members[name] = &Type{
|
||||||
|
object: obj,
|
||||||
|
pkg: pkg,
|
||||||
|
}
|
||||||
|
|
||||||
|
case *types.Const:
|
||||||
|
c := &NamedConst{
|
||||||
|
object: obj,
|
||||||
|
Value: NewConst(obj.Val(), obj.Type()),
|
||||||
|
pkg: pkg,
|
||||||
|
}
|
||||||
|
pkg.values[obj] = c.Value
|
||||||
|
pkg.Members[name] = c
|
||||||
|
|
||||||
|
case *types.Var:
|
||||||
|
g := &Global{
|
||||||
|
Pkg: pkg,
|
||||||
|
name: name,
|
||||||
|
object: obj,
|
||||||
|
typ: types.NewPointer(obj.Type()), // address
|
||||||
|
pos: obj.Pos(),
|
||||||
|
}
|
||||||
|
pkg.values[obj] = g
|
||||||
|
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 := &Function{
|
||||||
|
name: name,
|
||||||
|
object: obj,
|
||||||
|
Signature: sig,
|
||||||
|
syntax: syntax,
|
||||||
|
pos: obj.Pos(),
|
||||||
|
Pkg: pkg,
|
||||||
|
Prog: pkg.Prog,
|
||||||
|
}
|
||||||
|
if syntax == nil {
|
||||||
|
fn.Synthetic = "loaded from gc object file"
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.values[obj] = fn
|
||||||
|
if sig.Recv() == nil {
|
||||||
|
pkg.Members[name] = fn // package-level function
|
||||||
|
}
|
||||||
|
|
||||||
|
default: // (incl. *types.Package)
|
||||||
|
panic("unexpected Object type: " + obj.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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 {
|
||||||
|
if !isBlankIdent(id) {
|
||||||
|
memberFromObject(pkg, pkg.info.Defs[id], nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case token.VAR:
|
||||||
|
for _, spec := range decl.Specs {
|
||||||
|
for _, id := range spec.(*ast.ValueSpec).Names {
|
||||||
|
if !isBlankIdent(id) {
|
||||||
|
memberFromObject(pkg, pkg.info.Defs[id], spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case token.TYPE:
|
||||||
|
for _, spec := range decl.Specs {
|
||||||
|
id := spec.(*ast.TypeSpec).Name
|
||||||
|
if !isBlankIdent(id) {
|
||||||
|
memberFromObject(pkg, pkg.info.Defs[id], nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
id := decl.Name
|
||||||
|
if !isBlankIdent(id) {
|
||||||
|
memberFromObject(pkg, pkg.info.Defs[id], decl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePackage constructs and returns an SSA 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 SSA 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 {
|
||||||
|
p := &Package{
|
||||||
|
Prog: prog,
|
||||||
|
Members: make(map[string]Member),
|
||||||
|
values: make(map[types.Object]Value),
|
||||||
|
Pkg: pkg,
|
||||||
|
info: info, // transient (CREATE and BUILD phases)
|
||||||
|
files: files, // transient (CREATE and BUILD phases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add init() function.
|
||||||
|
p.init = &Function{
|
||||||
|
name: "init",
|
||||||
|
Signature: new(types.Signature),
|
||||||
|
Synthetic: "package initializer",
|
||||||
|
Pkg: p,
|
||||||
|
Prog: prog,
|
||||||
|
}
|
||||||
|
p.Members[p.init.name] = p.init
|
||||||
|
|
||||||
|
// CREATE phase.
|
||||||
|
// Allocate all package members: vars, funcs, consts and types.
|
||||||
|
if len(files) > 0 {
|
||||||
|
// Go source package.
|
||||||
|
for _, file := range files {
|
||||||
|
for _, decl := range file.Decls {
|
||||||
|
membersFromDecl(p, decl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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 {
|
||||||
|
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 in the
|
||||||
|
// program prog 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 SSA Package whose import
|
||||||
|
// path is path, or nil if no such SSA package has been created.
|
||||||
|
//
|
||||||
|
// Not all packages are importable. For example, no import
|
||||||
|
// declaration can resolve to the x_test package created by 'go test'
|
||||||
|
// or the ad-hoc main package created 'go build foo.go'.
|
||||||
|
//
|
||||||
|
func (prog *Program) ImportedPackage(path string) *Package {
|
||||||
|
return prog.imported[path]
|
||||||
|
}
|
123
vendor/honnef.co/go/tools/ssa/doc.go
vendored
Normal file
123
vendor/honnef.co/go/tools/ssa/doc.go
vendored
Normal file
|
@ -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 ssa 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.
|
||||||
|
//
|
||||||
|
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
|
||||||
|
//
|
||||||
|
// 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 SSA form is intentionally close to
|
||||||
|
// the source language to facilitate construction of source analysis
|
||||||
|
// tools. It is not intended for machine code generation.
|
||||||
|
//
|
||||||
|
// All looping, branching and switching constructs are replaced with
|
||||||
|
// unstructured control flow. Higher-level control flow constructs
|
||||||
|
// such as multi-way branch can be reconstructed as needed; see
|
||||||
|
// ssautil.Switches() for an example.
|
||||||
|
//
|
||||||
|
// To construct an SSA-form program, call ssautil.CreateProgram on a
|
||||||
|
// loader.Program, a set of type-checked packages created from
|
||||||
|
// parsed Go source files. The resulting ssa.Program contains all the
|
||||||
|
// packages and their members, but SSA code is not created for
|
||||||
|
// function bodies until a subsequent call to (*Package).Build.
|
||||||
|
//
|
||||||
|
// The builder initially builds a naive SSA 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 SSA 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 ✔ ✔
|
||||||
|
// *Builtin ✔
|
||||||
|
// *Call ✔ ✔
|
||||||
|
// *ChangeInterface ✔ ✔
|
||||||
|
// *ChangeType ✔ ✔
|
||||||
|
// *Const ✔
|
||||||
|
// *Convert ✔ ✔
|
||||||
|
// *DebugRef ✔
|
||||||
|
// *Defer ✔
|
||||||
|
// *Extract ✔ ✔
|
||||||
|
// *Field ✔ ✔
|
||||||
|
// *FieldAddr ✔ ✔
|
||||||
|
// *FreeVar ✔
|
||||||
|
// *Function ✔ ✔ (func)
|
||||||
|
// *Global ✔ ✔ (var)
|
||||||
|
// *Go ✔
|
||||||
|
// *If ✔
|
||||||
|
// *Index ✔ ✔
|
||||||
|
// *IndexAddr ✔ ✔
|
||||||
|
// *Jump ✔
|
||||||
|
// *Lookup ✔ ✔
|
||||||
|
// *MakeChan ✔ ✔
|
||||||
|
// *MakeClosure ✔ ✔
|
||||||
|
// *MakeInterface ✔ ✔
|
||||||
|
// *MakeMap ✔ ✔
|
||||||
|
// *MakeSlice ✔ ✔
|
||||||
|
// *MapUpdate ✔
|
||||||
|
// *NamedConst ✔ (const)
|
||||||
|
// *Next ✔ ✔
|
||||||
|
// *Panic ✔
|
||||||
|
// *Parameter ✔
|
||||||
|
// *Phi ✔ ✔
|
||||||
|
// *Range ✔ ✔
|
||||||
|
// *Return ✔
|
||||||
|
// *RunDefers ✔
|
||||||
|
// *Select ✔ ✔
|
||||||
|
// *Send ✔
|
||||||
|
// *Slice ✔ ✔
|
||||||
|
// *Store ✔
|
||||||
|
// *Type ✔ (type)
|
||||||
|
// *TypeAssert ✔ ✔
|
||||||
|
// *UnOp ✔ ✔
|
||||||
|
//
|
||||||
|
// 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 SSA and type graphs are semantically
|
||||||
|
// significant. (There is one exception: Ids, used to 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 ssa/ssautil package provides various utilities that depend only
|
||||||
|
// on the public API of this package.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): Consider the exceptional control-flow implications
|
||||||
|
// of defer and recover().
|
||||||
|
//
|
||||||
|
// 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 ssa // import "honnef.co/go/tools/ssa"
|
341
vendor/honnef.co/go/tools/ssa/dom.go
vendored
Normal file
341
vendor/honnef.co/go/tools/ssa/dom.go
vendored
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// http://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,
|
||||||
|
// http://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf
|
||||||
|
// to avoid the need for buckets of size > 1.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
type byDomPreorder []*BasicBlock
|
||||||
|
|
||||||
|
func (a byDomPreorder) Len() int { return len(a) }
|
||||||
|
func (a byDomPreorder) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a byDomPreorder) Less(i, j int) bool { return a[i].dom.pre < a[j].dom.pre }
|
||||||
|
|
||||||
|
// DomPreorder returns a new slice containing the blocks of f in
|
||||||
|
// dominator tree preorder.
|
||||||
|
//
|
||||||
|
func (f *Function) DomPreorder() []*BasicBlock {
|
||||||
|
n := len(f.Blocks)
|
||||||
|
order := make(byDomPreorder, n, n)
|
||||||
|
copy(order, f.Blocks)
|
||||||
|
sort.Sort(order)
|
||||||
|
return order
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(f *Function) {
|
||||||
|
// The step numbers refer to the original LT paper; the
|
||||||
|
// reordering is due to Georgiadis.
|
||||||
|
|
||||||
|
// Clear any previous domInfo.
|
||||||
|
for _, b := range f.Blocks {
|
||||||
|
b.dom = domInfo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(f.Blocks)
|
||||||
|
// Allocate space for 5 contiguous [n]*BasicBlock arrays:
|
||||||
|
// sdom, parent, ancestor, preorder, buckets.
|
||||||
|
space := make([]*BasicBlock, 5*n, 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 := f.Blocks[0]
|
||||||
|
prenum := lt.dfs(root, 0, preorder)
|
||||||
|
recover := f.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 f.Prog.mode&SanityCheckFunctions != 0 {
|
||||||
|
sanityCheckDomTree(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := 0; i < n; i++ {
|
||||||
|
for j := 0; j < n; j++ {
|
||||||
|
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 ----------------------------------------
|
||||||
|
|
||||||
|
// printDomTree prints the dominator tree as text, using indentation.
|
||||||
|
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.
|
||||||
|
func printDomTreeDot(buf *bytes.Buffer, 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, "}")
|
||||||
|
}
|
468
vendor/honnef.co/go/tools/ssa/emit.go
vendored
Normal file
468
vendor/honnef.co/go/tools/ssa/emit.go
vendored
Normal file
|
@ -0,0 +1,468 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// Helpers for emitting SSA instructions.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// emitNew emits to f a new (heap Alloc) instruction allocating an
|
||||||
|
// object of type typ. pos is the optional source location.
|
||||||
|
//
|
||||||
|
func emitNew(f *Function, typ types.Type, pos token.Pos) *Alloc {
|
||||||
|
v := &Alloc{Heap: true}
|
||||||
|
v.setType(types.NewPointer(typ))
|
||||||
|
v.setPos(pos)
|
||||||
|
f.emit(v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) *UnOp {
|
||||||
|
v := &UnOp{Op: token.MUL, X: addr}
|
||||||
|
v.setType(deref(addr.Type()))
|
||||||
|
f.emit(v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
if !f.debugInfo() {
|
||||||
|
return // debugging not enabled
|
||||||
|
}
|
||||||
|
if v == nil || e == nil {
|
||||||
|
panic("nil")
|
||||||
|
}
|
||||||
|
var obj types.Object
|
||||||
|
e = unparen(e)
|
||||||
|
if id, ok := e.(*ast.Ident); ok {
|
||||||
|
if isBlankIdent(id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obj = f.Pkg.objectOf(id)
|
||||||
|
switch obj.(type) {
|
||||||
|
case *types.Nil, *types.Const, *types.Builtin:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.emit(&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, pos token.Pos) Value {
|
||||||
|
switch op {
|
||||||
|
case token.SHL, token.SHR:
|
||||||
|
x = emitConv(f, x, t)
|
||||||
|
// y may be signed or an 'untyped' constant.
|
||||||
|
// TODO(adonovan): whence signed values?
|
||||||
|
if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUnsigned == 0 {
|
||||||
|
y = emitConv(f, y, types.Typ[types.Uint64])
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
y = emitConv(f, y, t)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("illegal op in emitArith: " + op.String())
|
||||||
|
|
||||||
|
}
|
||||||
|
v := &BinOp{
|
||||||
|
Op: op,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
}
|
||||||
|
v.setPos(pos)
|
||||||
|
v.setType(t)
|
||||||
|
return f.emit(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitCompare emits to f code compute the boolean result of
|
||||||
|
// comparison comparison 'x op y'.
|
||||||
|
//
|
||||||
|
func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) 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 == vTrue && op == token.EQL {
|
||||||
|
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 {
|
||||||
|
y = emitConv(f, y, x.Type())
|
||||||
|
} else if _, ok := yt.(*types.Interface); ok {
|
||||||
|
x = emitConv(f, x, y.Type())
|
||||||
|
} else if _, ok := x.(*Const); ok {
|
||||||
|
x = emitConv(f, x, y.Type())
|
||||||
|
} else if _, ok := y.(*Const); ok {
|
||||||
|
y = emitConv(f, y, x.Type())
|
||||||
|
} else {
|
||||||
|
// other cases, e.g. channels. No-op.
|
||||||
|
}
|
||||||
|
|
||||||
|
v := &BinOp{
|
||||||
|
Op: op,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
}
|
||||||
|
v.setPos(pos)
|
||||||
|
v.setType(tBool)
|
||||||
|
return f.emit(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 type.
|
||||||
|
//
|
||||||
|
func isValuePreserving(ut_src, ut_dst types.Type) bool {
|
||||||
|
// Identical underlying types?
|
||||||
|
if structTypesIdentical(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. Conversions cannot fail dynamically.
|
||||||
|
//
|
||||||
|
func emitConv(f *Function, val Value, typ types.Type) Value {
|
||||||
|
t_src := val.Type()
|
||||||
|
|
||||||
|
// Identical types? Conversion is a no-op.
|
||||||
|
if types.Identical(t_src, typ) {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
ut_dst := typ.Underlying()
|
||||||
|
ut_src := t_src.Underlying()
|
||||||
|
|
||||||
|
// Just a change of type, but not value or representation?
|
||||||
|
if isValuePreserving(ut_src, ut_dst) {
|
||||||
|
c := &ChangeType{X: val}
|
||||||
|
c.setType(typ)
|
||||||
|
return f.emit(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversion to, or construction of a value of, an interface type?
|
||||||
|
if _, ok := ut_dst.(*types.Interface); ok {
|
||||||
|
// Assignment from one interface type to another?
|
||||||
|
if _, ok := ut_src.(*types.Interface); ok {
|
||||||
|
c := &ChangeInterface{X: val}
|
||||||
|
c.setType(typ)
|
||||||
|
return f.emit(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Untyped nil constant? Return interface-typed nil constant.
|
||||||
|
if ut_src == tUntypedNil {
|
||||||
|
return nilConst(typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, DefaultType(ut_src))
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Pkg.Prog.needMethodsOf(val.Type())
|
||||||
|
mi := &MakeInterface{X: val}
|
||||||
|
mi.setType(typ)
|
||||||
|
return f.emit(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversion of a compile-time constant value?
|
||||||
|
if c, ok := val.(*Const); ok {
|
||||||
|
if _, ok := ut_dst.(*types.Basic); ok || c.IsNil() {
|
||||||
|
// 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, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're converting from constant to non-constant type,
|
||||||
|
// e.g. string -> []byte/[]rune.
|
||||||
|
}
|
||||||
|
|
||||||
|
// A representation-changing conversion?
|
||||||
|
// At least one of {ut_src,ut_dst} must be *Basic.
|
||||||
|
// (The other may be []byte or []rune.)
|
||||||
|
_, ok1 := ut_src.(*types.Basic)
|
||||||
|
_, ok2 := ut_dst.(*types.Basic)
|
||||||
|
if ok1 || ok2 {
|
||||||
|
c := &Convert{X: val}
|
||||||
|
c.setType(typ)
|
||||||
|
return f.emit(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, pos token.Pos) *Store {
|
||||||
|
s := &Store{
|
||||||
|
Addr: addr,
|
||||||
|
Val: emitConv(f, val, deref(addr.Type())),
|
||||||
|
pos: pos,
|
||||||
|
}
|
||||||
|
f.emit(s)
|
||||||
|
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) {
|
||||||
|
b := f.currentBlock
|
||||||
|
b.emit(new(Jump))
|
||||||
|
addEdge(b, target)
|
||||||
|
f.currentBlock = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
b := f.currentBlock
|
||||||
|
b.emit(&If{Cond: cond})
|
||||||
|
addEdge(b, tblock)
|
||||||
|
addEdge(b, fblock)
|
||||||
|
f.currentBlock = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) Value {
|
||||||
|
e := &Extract{Tuple: tuple, Index: index}
|
||||||
|
e.setType(tuple.Type().(*types.Tuple).At(index).Type())
|
||||||
|
return f.emit(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, pos token.Pos) Value {
|
||||||
|
a := &TypeAssert{X: x, AssertedType: t}
|
||||||
|
a.setPos(pos)
|
||||||
|
a.setType(t)
|
||||||
|
return f.emit(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, pos token.Pos) Value {
|
||||||
|
a := &TypeAssert{
|
||||||
|
X: x,
|
||||||
|
AssertedType: t,
|
||||||
|
CommaOk: true,
|
||||||
|
}
|
||||||
|
a.setPos(pos)
|
||||||
|
a.setType(types.NewTuple(
|
||||||
|
newVar("value", t),
|
||||||
|
varOk,
|
||||||
|
))
|
||||||
|
return f.emit(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
tresults := f.Signature.Results()
|
||||||
|
nr := tresults.Len()
|
||||||
|
if nr == 1 {
|
||||||
|
call.typ = tresults.At(0).Type()
|
||||||
|
} else {
|
||||||
|
call.typ = tresults
|
||||||
|
}
|
||||||
|
tuple := f.emit(call)
|
||||||
|
var ret Return
|
||||||
|
switch nr {
|
||||||
|
case 0:
|
||||||
|
// no-op
|
||||||
|
case 1:
|
||||||
|
ret.Results = []Value{tuple}
|
||||||
|
default:
|
||||||
|
for i := 0; i < nr; i++ {
|
||||||
|
v := emitExtract(f, tuple, i)
|
||||||
|
// 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)
|
||||||
|
f.currentBlock = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) Value {
|
||||||
|
for _, index := range indices {
|
||||||
|
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
||||||
|
|
||||||
|
if isPointer(v.Type()) {
|
||||||
|
instr := &FieldAddr{
|
||||||
|
X: v,
|
||||||
|
Field: index,
|
||||||
|
}
|
||||||
|
instr.setType(types.NewPointer(fld.Type()))
|
||||||
|
v = f.emit(instr)
|
||||||
|
// Load the field's value iff indirectly embedded.
|
||||||
|
if isPointer(fld.Type()) {
|
||||||
|
v = emitLoad(f, v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instr := &Field{
|
||||||
|
X: v,
|
||||||
|
Field: index,
|
||||||
|
}
|
||||||
|
instr.setType(fld.Type())
|
||||||
|
v = f.emit(instr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
||||||
|
if isPointer(v.Type()) {
|
||||||
|
instr := &FieldAddr{
|
||||||
|
X: v,
|
||||||
|
Field: index,
|
||||||
|
}
|
||||||
|
instr.setPos(id.Pos())
|
||||||
|
instr.setType(types.NewPointer(fld.Type()))
|
||||||
|
v = f.emit(instr)
|
||||||
|
// Load the field's value iff we don't want its address.
|
||||||
|
if !wantAddr {
|
||||||
|
v = emitLoad(f, v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instr := &Field{
|
||||||
|
X: v,
|
||||||
|
Field: index,
|
||||||
|
}
|
||||||
|
instr.setPos(id.Pos())
|
||||||
|
instr.setType(fld.Type())
|
||||||
|
v = f.emit(instr)
|
||||||
|
}
|
||||||
|
emitDebugRef(f, id, v, wantAddr)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// zeroValue emits to f code to produce a zero value of type t,
|
||||||
|
// and returns it.
|
||||||
|
//
|
||||||
|
func zeroValue(f *Function, t types.Type) Value {
|
||||||
|
switch t.Underlying().(type) {
|
||||||
|
case *types.Struct, *types.Array:
|
||||||
|
return emitLoad(f, f.addLocal(t, token.NoPos))
|
||||||
|
default:
|
||||||
|
return zeroConst(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if f.namedResults != nil {
|
||||||
|
// Reload NRPs to form value tuple.
|
||||||
|
for _, r := range f.namedResults {
|
||||||
|
results = append(results, emitLoad(f, r))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
R := f.Signature.Results()
|
||||||
|
for i, n := 0, R.Len(); i < n; i++ {
|
||||||
|
T := R.At(i).Type()
|
||||||
|
|
||||||
|
// Return zero value of each result type.
|
||||||
|
results = append(results, zeroValue(f, T))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.emit(&Return{Results: results})
|
||||||
|
|
||||||
|
f.currentBlock = saved
|
||||||
|
}
|
701
vendor/honnef.co/go/tools/ssa/func.go
vendored
Normal file
701
vendor/honnef.co/go/tools/ssa/func.go
vendored
Normal file
|
@ -0,0 +1,701 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// This file implements the Function and BasicBlock types.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent returns the function that contains block b.
|
||||||
|
func (b *BasicBlock) Parent() *Function { return b.parent }
|
||||||
|
|
||||||
|
// 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) Value {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasicBlock) RemovePred(p *BasicBlock) {
|
||||||
|
b.removePred(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
type lblock struct {
|
||||||
|
_goto *BasicBlock
|
||||||
|
_break *BasicBlock
|
||||||
|
_continue *BasicBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// labelledBlock returns the branch target associated with the
|
||||||
|
// specified label, creating it if needed.
|
||||||
|
//
|
||||||
|
func (f *Function) labelledBlock(label *ast.Ident) *lblock {
|
||||||
|
lb := f.lblocks[label.Obj]
|
||||||
|
if lb == nil {
|
||||||
|
lb = &lblock{_goto: f.newBasicBlock(label.Name)}
|
||||||
|
if f.lblocks == nil {
|
||||||
|
f.lblocks = make(map[*ast.Object]*lblock)
|
||||||
|
}
|
||||||
|
f.lblocks[label.Obj] = lb
|
||||||
|
}
|
||||||
|
return lb
|
||||||
|
}
|
||||||
|
|
||||||
|
// addParam adds a (non-escaping) parameter to f.Params of the
|
||||||
|
// specified name, type and source position.
|
||||||
|
//
|
||||||
|
func (f *Function) addParam(name string, typ types.Type, pos token.Pos) *Parameter {
|
||||||
|
v := &Parameter{
|
||||||
|
name: name,
|
||||||
|
typ: typ,
|
||||||
|
pos: pos,
|
||||||
|
parent: f,
|
||||||
|
}
|
||||||
|
f.Params = append(f.Params, v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Function) addParamObj(obj types.Object) *Parameter {
|
||||||
|
name := obj.Name()
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("arg%d", len(f.Params))
|
||||||
|
}
|
||||||
|
param := f.addParam(name, obj.Type(), obj.Pos())
|
||||||
|
param.object = obj
|
||||||
|
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.Object) {
|
||||||
|
param := f.addParamObj(obj)
|
||||||
|
spill := &Alloc{Comment: obj.Name()}
|
||||||
|
spill.setType(types.NewPointer(obj.Type()))
|
||||||
|
spill.setPos(obj.Pos())
|
||||||
|
f.objects[obj] = spill
|
||||||
|
f.Locals = append(f.Locals, spill)
|
||||||
|
f.emit(spill)
|
||||||
|
f.emit(&Store{Addr: spill, Val: param})
|
||||||
|
}
|
||||||
|
|
||||||
|
// startBody initializes the function prior to generating SSA code for its body.
|
||||||
|
// Precondition: f.Type() already set.
|
||||||
|
//
|
||||||
|
func (f *Function) startBody() {
|
||||||
|
f.currentBlock = f.newBasicBlock("entry")
|
||||||
|
f.objects = make(map[types.Object]Value) // needed for some synthetics, e.g. init
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// 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(f.Pkg.info.Defs[n])
|
||||||
|
}
|
||||||
|
// Anonymous receiver? No need to spill.
|
||||||
|
if field.Names == nil {
|
||||||
|
f.addParamObj(f.Signature.Recv())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(f.Pkg.info.Defs[n])
|
||||||
|
}
|
||||||
|
// Anonymous parameter? No need to spill.
|
||||||
|
if field.Names == nil {
|
||||||
|
f.addParamObj(f.Signature.Params().At(len(f.Params) - n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named results.
|
||||||
|
if functype.Results != nil {
|
||||||
|
for _, field := range functype.Results.List {
|
||||||
|
// Implicit "var" decl of locals for named results.
|
||||||
|
for _, n := range field.Names {
|
||||||
|
f.namedResults = append(f.namedResults, f.addLocalForIdent(n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// numberRegisters assigns numbers to all SSA registers
|
||||||
|
// (value-defining Instructions) in f, to aid debugging.
|
||||||
|
// (Non-Instruction Values are named at construction.)
|
||||||
|
//
|
||||||
|
func numberRegisters(f *Function) {
|
||||||
|
v := 0
|
||||||
|
for _, b := range f.Blocks {
|
||||||
|
for _, instr := range b.Instrs {
|
||||||
|
switch instr.(type) {
|
||||||
|
case Value:
|
||||||
|
instr.(interface {
|
||||||
|
setNum(int)
|
||||||
|
}).setNum(v)
|
||||||
|
v++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
for _, rand := range rands {
|
||||||
|
if r := *rand; r != nil {
|
||||||
|
if ref := r.Referrers(); ref != nil {
|
||||||
|
*ref = append(*ref, instr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finishBody() finalizes the function after SSA code generation of its body.
|
||||||
|
func (f *Function) finishBody() {
|
||||||
|
f.objects = nil
|
||||||
|
f.currentBlock = nil
|
||||||
|
f.lblocks = nil
|
||||||
|
|
||||||
|
// Don't pin the AST in memory (except in debug mode).
|
||||||
|
if n := f.syntax; n != nil && !f.debugInfo() {
|
||||||
|
f.syntax = extentNode{n.Pos(), n.End()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
if f.Prog.mode&NaiveForm == 0 {
|
||||||
|
// For debugging pre-state of lifting pass:
|
||||||
|
// numberRegisters(f)
|
||||||
|
// f.WriteTo(os.Stderr)
|
||||||
|
lift(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.namedResults = nil // (used by lifting)
|
||||||
|
|
||||||
|
numberRegisters(f)
|
||||||
|
|
||||||
|
if f.Prog.mode&PrintFunctions != 0 {
|
||||||
|
printMu.Lock()
|
||||||
|
f.WriteTo(os.Stdout)
|
||||||
|
printMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Prog.mode&SanityCheckFunctions != 0 {
|
||||||
|
mustSanityCheck(f, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Function) RemoveNilBlocks() {
|
||||||
|
f.removeNilBlocks()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// TODO(adonovan): do we want ast.File granularity?
|
||||||
|
pkg.debug = debug
|
||||||
|
}
|
||||||
|
|
||||||
|
// debugInfo reports whether debug info is wanted for this function.
|
||||||
|
func (f *Function) debugInfo() bool {
|
||||||
|
return f.Pkg != nil && f.Pkg.debug
|
||||||
|
}
|
||||||
|
|
||||||
|
// addNamedLocal creates a local variable, adds it to function f and
|
||||||
|
// returns it. Its name and type are taken from obj. Subsequent
|
||||||
|
// calls to f.lookup(obj) will return the same local.
|
||||||
|
//
|
||||||
|
func (f *Function) addNamedLocal(obj types.Object) *Alloc {
|
||||||
|
l := f.addLocal(obj.Type(), obj.Pos())
|
||||||
|
l.Comment = obj.Name()
|
||||||
|
f.objects[obj] = l
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Function) addLocalForIdent(id *ast.Ident) *Alloc {
|
||||||
|
return f.addNamedLocal(f.Pkg.info.Defs[id])
|
||||||
|
}
|
||||||
|
|
||||||
|
// addLocal creates an anonymous local variable of type typ, adds it
|
||||||
|
// to function f and returns it. pos is the optional source location.
|
||||||
|
//
|
||||||
|
func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc {
|
||||||
|
v := &Alloc{}
|
||||||
|
v.setType(types.NewPointer(typ))
|
||||||
|
v.setPos(pos)
|
||||||
|
f.Locals = append(f.Locals, v)
|
||||||
|
f.emit(v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
func (f *Function) lookup(obj types.Object, escaping bool) Value {
|
||||||
|
if v, ok := f.objects[obj]; ok {
|
||||||
|
if alloc, ok := v.(*Alloc); ok && escaping {
|
||||||
|
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 ssa.Value for " + obj.String())
|
||||||
|
}
|
||||||
|
outer := f.parent.lookup(obj, true) // escaping
|
||||||
|
v := &FreeVar{
|
||||||
|
name: obj.Name(),
|
||||||
|
typ: outer.Type(),
|
||||||
|
pos: outer.Pos(),
|
||||||
|
outer: outer,
|
||||||
|
parent: f,
|
||||||
|
}
|
||||||
|
f.objects[obj] = v
|
||||||
|
f.FreeVars = append(f.FreeVars, v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit emits the specified instruction to function f.
|
||||||
|
func (f *Function) emit(instr Instruction) Value {
|
||||||
|
return f.currentBlock.emit(instr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.pkg(); 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, params []*Parameter) {
|
||||||
|
buf.WriteString("func ")
|
||||||
|
if recv := sig.Recv(); recv != nil {
|
||||||
|
buf.WriteString("(")
|
||||||
|
if n := params[0].Name(); n != "" {
|
||||||
|
buf.WriteString(n)
|
||||||
|
buf.WriteString(" ")
|
||||||
|
}
|
||||||
|
types.WriteType(buf, params[0].Type(), types.RelativeTo(from))
|
||||||
|
buf.WriteString(") ")
|
||||||
|
}
|
||||||
|
buf.WriteString(name)
|
||||||
|
types.WriteSignature(buf, sig, types.RelativeTo(from))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Function) pkg() *types.Package {
|
||||||
|
if f.Pkg != nil {
|
||||||
|
return f.Pkg.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())
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Recover != nil {
|
||||||
|
fmt.Fprintf(buf, "# Recover: %s\n", f.Recover)
|
||||||
|
}
|
||||||
|
|
||||||
|
from := f.pkg()
|
||||||
|
|
||||||
|
if f.FreeVars != nil {
|
||||||
|
buf.WriteString("# Free variables:\n")
|
||||||
|
for i, fv := range f.FreeVars {
|
||||||
|
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, 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:\t%s %s\n", i, l.Name(), relType(deref(l.Type()), from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeSignature(buf, from, f.Name(), f.Signature, f.Params)
|
||||||
|
buf.WriteString(":\n")
|
||||||
|
|
||||||
|
if f.Blocks == nil {
|
||||||
|
buf.WriteString("\t(external)\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB. column calculations are confused by non-ASCII
|
||||||
|
// characters and assume 8-space tabs.
|
||||||
|
const punchcard = 80 // for old time's sake.
|
||||||
|
const tabwidth = 8
|
||||||
|
for _, b := range f.Blocks {
|
||||||
|
if b == nil {
|
||||||
|
// Corrupt CFG.
|
||||||
|
fmt.Fprintf(buf, ".nil:\n")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n, _ := fmt.Fprintf(buf, "%d:", b.Index)
|
||||||
|
bmsg := fmt.Sprintf("%s P:%d S:%d", b.Comment, len(b.Preds), len(b.Succs))
|
||||||
|
fmt.Fprintf(buf, "%*s%s\n", punchcard-1-n-len(bmsg), "", bmsg)
|
||||||
|
|
||||||
|
if false { // CFG debugging
|
||||||
|
fmt.Fprintf(buf, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs)
|
||||||
|
}
|
||||||
|
for _, instr := range b.Instrs {
|
||||||
|
buf.WriteString("\t")
|
||||||
|
switch v := instr.(type) {
|
||||||
|
case Value:
|
||||||
|
l := punchcard - tabwidth
|
||||||
|
// 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("<deleted>")
|
||||||
|
default:
|
||||||
|
buf.WriteString(instr.String())
|
||||||
|
}
|
||||||
|
buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buf, "\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
|
||||||
|
// SSA 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}
|
||||||
|
}
|
||||||
|
|
||||||
|
type extentNode [2]token.Pos
|
||||||
|
|
||||||
|
func (n extentNode) Pos() token.Pos { return n[0] }
|
||||||
|
func (n extentNode) End() token.Pos { return n[1] }
|
||||||
|
|
||||||
|
// Syntax returns an ast.Node whose Pos/End methods provide the
|
||||||
|
// lexical extent of the function if it was defined by Go source code
|
||||||
|
// (f.Synthetic==""), or nil otherwise.
|
||||||
|
//
|
||||||
|
// If f was built with debug information (see Package.SetDebugRef),
|
||||||
|
// the result is the *ast.FuncDecl or *ast.FuncLit that declared the
|
||||||
|
// function. Otherwise, it is an opaque Node providing only position
|
||||||
|
// information; this avoids pinning the AST in memory.
|
||||||
|
//
|
||||||
|
func (f *Function) Syntax() ast.Node { return f.syntax }
|
7
vendor/honnef.co/go/tools/ssa/identical.go
vendored
Normal file
7
vendor/honnef.co/go/tools/ssa/identical.go
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package ssa
|
||||||
|
|
||||||
|
import "go/types"
|
||||||
|
|
||||||
|
var structTypesIdentical = types.IdenticalIgnoreTags
|
7
vendor/honnef.co/go/tools/ssa/identical_17.go
vendored
Normal file
7
vendor/honnef.co/go/tools/ssa/identical_17.go
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package ssa
|
||||||
|
|
||||||
|
import "go/types"
|
||||||
|
|
||||||
|
var structTypesIdentical = types.Identical
|
653
vendor/honnef.co/go/tools/ssa/lift.go
vendored
Normal file
653
vendor/honnef.co/go/tools/ssa/lift.go
vendored
Normal file
|
@ -0,0 +1,653 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// 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...
|
||||||
|
// http://doi.acm.org/10.1145/115372.115320
|
||||||
|
//
|
||||||
|
// Cooper, Harvey, Kennedy. 2001. A Simple, Fast Dominance Algorithm.
|
||||||
|
// Software Practice and Experience 2001, 4:1-10.
|
||||||
|
// http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
|
||||||
|
//
|
||||||
|
// Daniel Berlin, llvmdev mailing list, 2012.
|
||||||
|
// http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html
|
||||||
|
// (Be sure to expand the whole thread.)
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 [][]*BasicBlock
|
||||||
|
|
||||||
|
func (df domFrontier) add(u, v *BasicBlock) {
|
||||||
|
p := &df[u.Index]
|
||||||
|
*p = append(*p, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// build builds the dominance frontier df for the dominator (sub)tree
|
||||||
|
// rooted at u, using the Cytron et al. algorithm.
|
||||||
|
//
|
||||||
|
// 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(u *BasicBlock) {
|
||||||
|
// Encounter each node u in postorder of dom tree.
|
||||||
|
for _, child := range u.dom.children {
|
||||||
|
df.build(child)
|
||||||
|
}
|
||||||
|
for _, vb := range u.Succs {
|
||||||
|
if v := vb.dom; v.idom != u {
|
||||||
|
df.add(u, vb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, w := range u.dom.children {
|
||||||
|
for _, vb := range df[w.Index] {
|
||||||
|
// TODO(adonovan): opt: use word-parallel bitwise union.
|
||||||
|
if v := vb.dom; v.idom != u {
|
||||||
|
df.add(u, vb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDomFrontier(fn *Function) domFrontier {
|
||||||
|
df := make(domFrontier, len(fn.Blocks))
|
||||||
|
df.build(fn.Blocks[0])
|
||||||
|
if fn.Recover != nil {
|
||||||
|
df.build(fn.Recover)
|
||||||
|
}
|
||||||
|
return df
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeInstr(refs []Instruction, instr Instruction) []Instruction {
|
||||||
|
i := 0
|
||||||
|
for _, ref := range refs {
|
||||||
|
if ref == instr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
refs[i] = ref
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for j := i; j != len(refs); j++ {
|
||||||
|
refs[j] = nil // aid GC
|
||||||
|
}
|
||||||
|
return refs[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// lift replaces local and new Allocs accessed only with
|
||||||
|
// load/store by SSA registers, inserting φ-nodes where necessary.
|
||||||
|
// The result is a program in classical 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) {
|
||||||
|
// 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.
|
||||||
|
df := buildDomFrontier(fn)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newPhis := make(newPhiMap)
|
||||||
|
|
||||||
|
// 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 in functions that contain no 'defer'
|
||||||
|
// instructions.
|
||||||
|
usesDefer := false
|
||||||
|
|
||||||
|
// A counter used to generate ~unique ids for Phi nodes, as an
|
||||||
|
// aid to debugging. We use large numbers to make them highly
|
||||||
|
// visible. All nodes are renumbered later.
|
||||||
|
fresh := 1000
|
||||||
|
|
||||||
|
// Determine which allocs we can lift and number them densely.
|
||||||
|
// The renaming phase uses this numbering for compact maps.
|
||||||
|
numAllocs := 0
|
||||||
|
for _, b := range fn.Blocks {
|
||||||
|
b.gaps = 0
|
||||||
|
b.rundefers = 0
|
||||||
|
for _, instr := range b.Instrs {
|
||||||
|
switch instr := instr.(type) {
|
||||||
|
case *Alloc:
|
||||||
|
index := -1
|
||||||
|
if liftAlloc(df, instr, newPhis, &fresh) {
|
||||||
|
index = numAllocs
|
||||||
|
numAllocs++
|
||||||
|
}
|
||||||
|
instr.index = index
|
||||||
|
case *Defer:
|
||||||
|
usesDefer = true
|
||||||
|
case *RunDefers:
|
||||||
|
b.rundefers++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// Eliminate dead φ-nodes.
|
||||||
|
removeDeadPhis(fn.Blocks, newPhis)
|
||||||
|
|
||||||
|
// Prepend remaining live φ-nodes to each block.
|
||||||
|
for _, b := range fn.Blocks {
|
||||||
|
nps := newPhis[b]
|
||||||
|
j := len(nps)
|
||||||
|
|
||||||
|
rundefersToKill := b.rundefers
|
||||||
|
if usesDefer {
|
||||||
|
rundefersToKill = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if j+b.gaps+rundefersToKill == 0 {
|
||||||
|
continue // fast path: no new phis or gaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact nps + non-nil Instrs into a new slice.
|
||||||
|
// TODO(adonovan): opt: compact in situ (rightwards)
|
||||||
|
// if Instrs has sufficient space or slack.
|
||||||
|
dst := make([]Instruction, len(b.Instrs)+j-b.gaps-rundefersToKill)
|
||||||
|
for i, np := range nps {
|
||||||
|
dst[i] = np.phi
|
||||||
|
}
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeDeadPhis removes φ-nodes not transitively needed by a
|
||||||
|
// non-Phi, non-DebugRef instruction.
|
||||||
|
func removeDeadPhis(blocks []*BasicBlock, newPhis newPhiMap) {
|
||||||
|
// First pass: find the set of "live" φ-nodes: those reachable
|
||||||
|
// from some non-Phi instruction.
|
||||||
|
//
|
||||||
|
// We compute reachability in reverse, starting from each φ,
|
||||||
|
// rather than forwards, starting from each live non-Phi
|
||||||
|
// instruction, because this way visits much less of the
|
||||||
|
// Value graph.
|
||||||
|
livePhis := make(map[*Phi]bool)
|
||||||
|
for _, npList := range newPhis {
|
||||||
|
for _, np := range npList {
|
||||||
|
phi := np.phi
|
||||||
|
if !livePhis[phi] && phiHasDirectReferrer(phi) {
|
||||||
|
markLivePhi(livePhis, 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(livePhis, phi.(*Phi))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: eliminate unused phis from newPhis.
|
||||||
|
for block, npList := range newPhis {
|
||||||
|
j := 0
|
||||||
|
for _, np := range npList {
|
||||||
|
if livePhis[np.phi] {
|
||||||
|
npList[j] = np
|
||||||
|
j++
|
||||||
|
} else {
|
||||||
|
// discard it, first removing it from referrers
|
||||||
|
for _, val := range np.phi.Edges {
|
||||||
|
if refs := val.Referrers(); refs != nil {
|
||||||
|
*refs = removeInstr(*refs, np.phi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
np.phi.block = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newPhis[block] = npList[:j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// markLivePhi marks phi, and all φ-nodes transitively reachable via
|
||||||
|
// its Operands, live.
|
||||||
|
func markLivePhi(livePhis map[*Phi]bool, phi *Phi) {
|
||||||
|
livePhis[phi] = true
|
||||||
|
for _, rand := range phi.Operands(nil) {
|
||||||
|
if q, ok := (*rand).(*Phi); ok {
|
||||||
|
if !livePhis[q] {
|
||||||
|
markLivePhi(livePhis, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// phiHasDirectReferrer reports whether phi is directly referred to by
|
||||||
|
// a non-Phi instruction. Such instructions are the
|
||||||
|
// roots of the liveness traversal.
|
||||||
|
func phiHasDirectReferrer(phi *Phi) bool {
|
||||||
|
for _, instr := range *phi.Referrers() {
|
||||||
|
if _, ok := instr.(*Phi); !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockSet struct{ big.Int } // (inherit methods from Int)
|
||||||
|
|
||||||
|
// add adds b to the set and returns true if the set changed.
|
||||||
|
func (s *blockSet) add(b *BasicBlock) bool {
|
||||||
|
i := b.Index
|
||||||
|
if s.Bit(i) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s.SetBit(&s.Int, i, 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// take removes an arbitrary element from a set s and
|
||||||
|
// returns its index, or returns -1 if empty.
|
||||||
|
func (s *blockSet) take() int {
|
||||||
|
l := s.BitLen()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
if s.Bit(i) == 1 {
|
||||||
|
s.SetBit(&s.Int, i, 0)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPhiMap records for each basic block, the set of newPhis that
|
||||||
|
// must be prepended to the block.
|
||||||
|
type newPhiMap map[*BasicBlock][]newPhi
|
||||||
|
|
||||||
|
// liftAlloc determines whether alloc can be lifted into registers,
|
||||||
|
// and if so, it populates newPhis with all the φ-nodes it may require
|
||||||
|
// and returns true.
|
||||||
|
//
|
||||||
|
// fresh is a source of fresh ids for phi nodes.
|
||||||
|
//
|
||||||
|
func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool {
|
||||||
|
// Don't lift aggregates into registers, because we don't have
|
||||||
|
// a way to express their zero-constants.
|
||||||
|
switch deref(alloc.Type()).Underlying().(type) {
|
||||||
|
case *types.Array, *types.Struct:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't lift named return values in functions that defer
|
||||||
|
// calls that may recover from panic.
|
||||||
|
if fn := alloc.Parent(); fn.Recover != nil {
|
||||||
|
for _, nr := range fn.namedResults {
|
||||||
|
if nr == alloc {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute defblocks, the set of blocks containing a
|
||||||
|
// definition of the alloc cell.
|
||||||
|
var defblocks blockSet
|
||||||
|
for _, instr := range *alloc.Referrers() {
|
||||||
|
// Bail out if we discover the alloc is not liftable;
|
||||||
|
// the only operations permitted to use the alloc are
|
||||||
|
// loads/stores into the cell, and DebugRef.
|
||||||
|
switch instr := instr.(type) {
|
||||||
|
case *Store:
|
||||||
|
if instr.Val == alloc {
|
||||||
|
return false // address used as value
|
||||||
|
}
|
||||||
|
if instr.Addr != alloc {
|
||||||
|
panic("Alloc.Referrers is inconsistent")
|
||||||
|
}
|
||||||
|
defblocks.add(instr.Block())
|
||||||
|
case *UnOp:
|
||||||
|
if instr.Op != token.MUL {
|
||||||
|
return false // not a load
|
||||||
|
}
|
||||||
|
if instr.X != alloc {
|
||||||
|
panic("Alloc.Referrers is inconsistent")
|
||||||
|
}
|
||||||
|
case *DebugRef:
|
||||||
|
// ok
|
||||||
|
default:
|
||||||
|
return false // some other instruction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := alloc.Parent()
|
||||||
|
|
||||||
|
// Φ-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.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): opt: recycle slice storage for W,
|
||||||
|
// hasAlready, defBlocks across liftAlloc calls.
|
||||||
|
var hasAlready blockSet
|
||||||
|
|
||||||
|
// Initialize W and work to defblocks.
|
||||||
|
var work blockSet = defblocks // blocks seen
|
||||||
|
var W blockSet // blocks to do
|
||||||
|
W.Set(&defblocks.Int)
|
||||||
|
|
||||||
|
// Traverse iterated dominance frontier, inserting φ-nodes.
|
||||||
|
for i := W.take(); i != -1; i = W.take() {
|
||||||
|
u := fn.Blocks[i]
|
||||||
|
for _, v := range df[u.Index] {
|
||||||
|
if hasAlready.add(v) {
|
||||||
|
// Create φ-node.
|
||||||
|
// It will be prepended to v.Instrs later, if needed.
|
||||||
|
phi := &Phi{
|
||||||
|
Edges: make([]Value, len(v.Preds)),
|
||||||
|
Comment: alloc.Comment,
|
||||||
|
}
|
||||||
|
// This is merely a debugging aid:
|
||||||
|
phi.setNum(*fresh)
|
||||||
|
*fresh++
|
||||||
|
|
||||||
|
phi.pos = alloc.Pos()
|
||||||
|
phi.setType(deref(alloc.Type()))
|
||||||
|
phi.block = v
|
||||||
|
if debugLifting {
|
||||||
|
fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, v)
|
||||||
|
}
|
||||||
|
newPhis[v] = append(newPhis[v], newPhi{phi, alloc})
|
||||||
|
|
||||||
|
if work.add(v) {
|
||||||
|
W.add(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// renamed returns the value to which alloc is being renamed,
|
||||||
|
// constructing it lazily if it's the implicit zero initialization.
|
||||||
|
//
|
||||||
|
func renamed(renaming []Value, alloc *Alloc) Value {
|
||||||
|
v := renaming[alloc.index]
|
||||||
|
if v == nil {
|
||||||
|
v = zeroConst(deref(alloc.Type()))
|
||||||
|
renaming[alloc.index] = v
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename implements the (Cytron et al) 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. For lifting, we need only consider loads,
|
||||||
|
// stores and φ-nodes.
|
||||||
|
//
|
||||||
|
// 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 newPhiMap) {
|
||||||
|
// Each φ-node becomes the new name for its associated Alloc.
|
||||||
|
for _, np := range newPhis[u] {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
// Remove the store from the referrer list of the stored value.
|
||||||
|
if refs := instr.Val.Referrers(); refs != nil {
|
||||||
|
*refs = removeInstr(*refs, instr)
|
||||||
|
}
|
||||||
|
// Delete the Store.
|
||||||
|
u.Instrs[i] = nil
|
||||||
|
u.gaps++
|
||||||
|
}
|
||||||
|
|
||||||
|
case *UnOp:
|
||||||
|
if instr.Op == token.MUL {
|
||||||
|
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell
|
||||||
|
newval := renamed(renaming, alloc)
|
||||||
|
if debugLifting {
|
||||||
|
fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n",
|
||||||
|
instr.Name(), instr, newval.Name())
|
||||||
|
}
|
||||||
|
// Replace all references to
|
||||||
|
// the loaded value by the
|
||||||
|
// dominating stored value.
|
||||||
|
replaceAll(instr, newval)
|
||||||
|
// Delete the Load.
|
||||||
|
u.Instrs[i] = nil
|
||||||
|
u.gaps++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *DebugRef:
|
||||||
|
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // ref of Alloc cell
|
||||||
|
if instr.IsAddr {
|
||||||
|
instr.X = renamed(renaming, alloc)
|
||||||
|
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]
|
||||||
|
if len(phis) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i := v.predIndex(u)
|
||||||
|
for _, np := range phis {
|
||||||
|
phi := np.phi
|
||||||
|
alloc := np.alloc
|
||||||
|
newval := renamed(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 i, v := range u.dom.children {
|
||||||
|
r := renaming
|
||||||
|
if i < len(u.dom.children)-1 {
|
||||||
|
// On all but the final iteration, we must make
|
||||||
|
// a copy to avoid destructive update.
|
||||||
|
r = make([]Value, len(renaming))
|
||||||
|
copy(r, renaming)
|
||||||
|
}
|
||||||
|
rename(v, r, newPhis)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
123
vendor/honnef.co/go/tools/ssa/lvalue.go
vendored
Normal file
123
vendor/honnef.co/go/tools/ssa/lvalue.go
vendored
Normal file
|
@ -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 ssa
|
||||||
|
|
||||||
|
// lvalues are the union of addressable expressions and map-index
|
||||||
|
// expressions.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"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) // stores v into the location
|
||||||
|
load(fn *Function) 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
|
||||||
|
pos token.Pos // source position
|
||||||
|
expr ast.Expr // source syntax of the value (not address) [debug mode]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *address) load(fn *Function) Value {
|
||||||
|
load := emitLoad(fn, a.addr)
|
||||||
|
load.pos = a.pos
|
||||||
|
return load
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *address) store(fn *Function, v Value) {
|
||||||
|
store := emitStore(fn, a.addr, v, a.pos)
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
// An element is an lvalue represented by m[k], the location of an
|
||||||
|
// element of a map or string. These locations are not addressable
|
||||||
|
// since pointers cannot be formed from them, but they do support
|
||||||
|
// load(), and in the case of maps, store().
|
||||||
|
//
|
||||||
|
type element struct {
|
||||||
|
m, k Value // map or string
|
||||||
|
t types.Type // map element type or string byte type
|
||||||
|
pos token.Pos // source position of colon ({k:v}) or lbrack (m[k]=v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *element) load(fn *Function) Value {
|
||||||
|
l := &Lookup{
|
||||||
|
X: e.m,
|
||||||
|
Index: e.k,
|
||||||
|
}
|
||||||
|
l.setPos(e.pos)
|
||||||
|
l.setType(e.t)
|
||||||
|
return fn.emit(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *element) store(fn *Function, v Value) {
|
||||||
|
up := &MapUpdate{
|
||||||
|
Map: e.m,
|
||||||
|
Key: e.k,
|
||||||
|
Value: emitConv(fn, v, e.t),
|
||||||
|
}
|
||||||
|
up.pos = e.pos
|
||||||
|
fn.emit(up)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *element) address(fn *Function) Value {
|
||||||
|
panic("map/string elements are not addressable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *element) typ() types.Type {
|
||||||
|
return e.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) Value {
|
||||||
|
panic("blank.load is illegal")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl blank) store(fn *Function, v Value) {
|
||||||
|
s := &BlankStore{
|
||||||
|
Val: v,
|
||||||
|
}
|
||||||
|
fn.emit(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
239
vendor/honnef.co/go/tools/ssa/methods.go
vendored
Normal file
239
vendor/honnef.co/go/tools/ssa/methods.go
vendored
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// This file defines utilities for population of method sets.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MethodValue returns the Function implementing method sel, building
|
||||||
|
// wrapper methods on demand. It returns nil if sel denotes an
|
||||||
|
// abstract (interface) method.
|
||||||
|
//
|
||||||
|
// Precondition: sel.Kind() == MethodVal.
|
||||||
|
//
|
||||||
|
// Thread-safe.
|
||||||
|
//
|
||||||
|
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||||
|
//
|
||||||
|
func (prog *Program) MethodValue(sel *types.Selection) *Function {
|
||||||
|
if sel.Kind() != types.MethodVal {
|
||||||
|
panic(fmt.Sprintf("Method(%s) kind != MethodVal", sel))
|
||||||
|
}
|
||||||
|
T := sel.Recv()
|
||||||
|
if isInterface(T) {
|
||||||
|
return nil // abstract method
|
||||||
|
}
|
||||||
|
if prog.mode&LogSource != 0 {
|
||||||
|
defer logStack("Method %s %v", T, sel)()
|
||||||
|
}
|
||||||
|
|
||||||
|
prog.methodsMu.Lock()
|
||||||
|
defer prog.methodsMu.Unlock()
|
||||||
|
|
||||||
|
return prog.addMethod(prog.createMethodSet(T), sel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupMethod returns the implementation of the method of type T
|
||||||
|
// identified by (pkg, name). It returns nil if the method exists but
|
||||||
|
// is abstract, 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 non-interface type.
|
||||||
|
type methodSet struct {
|
||||||
|
mapping map[string]*Function // populated lazily
|
||||||
|
complete bool // mapping contains all methods
|
||||||
|
}
|
||||||
|
|
||||||
|
// Precondition: !isInterface(T).
|
||||||
|
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||||
|
func (prog *Program) createMethodSet(T types.Type) *methodSet {
|
||||||
|
mset, ok := prog.methodSets.At(T).(*methodSet)
|
||||||
|
if !ok {
|
||||||
|
mset = &methodSet{mapping: make(map[string]*Function)}
|
||||||
|
prog.methodSets.Set(T, mset)
|
||||||
|
}
|
||||||
|
return mset
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||||
|
func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function {
|
||||||
|
if sel.Kind() == types.MethodExpr {
|
||||||
|
panic(sel)
|
||||||
|
}
|
||||||
|
id := sel.Obj().Id()
|
||||||
|
fn := mset.mapping[id]
|
||||||
|
if fn == nil {
|
||||||
|
obj := sel.Obj().(*types.Func)
|
||||||
|
|
||||||
|
needsPromotion := len(sel.Index()) > 1
|
||||||
|
needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv())
|
||||||
|
if needsPromotion || needsIndirection {
|
||||||
|
fn = makeWrapper(prog, sel)
|
||||||
|
} else {
|
||||||
|
fn = prog.declaredFunc(obj)
|
||||||
|
}
|
||||||
|
if fn.Signature.Recv() == nil {
|
||||||
|
panic(fn) // missing receiver
|
||||||
|
}
|
||||||
|
mset.mapping[id] = fn
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeTypes returns a new unordered slice containing all
|
||||||
|
// concrete types in the program for which a complete (non-empty)
|
||||||
|
// method set is required at run-time.
|
||||||
|
//
|
||||||
|
// Thread-safe.
|
||||||
|
//
|
||||||
|
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||||
|
//
|
||||||
|
func (prog *Program) RuntimeTypes() []types.Type {
|
||||||
|
prog.methodsMu.Lock()
|
||||||
|
defer prog.methodsMu.Unlock()
|
||||||
|
|
||||||
|
var res []types.Type
|
||||||
|
prog.methodSets.Iterate(func(T types.Type, v interface{}) {
|
||||||
|
if v.(*methodSet).complete {
|
||||||
|
res = append(res, T)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// declaredFunc returns the concrete function/method denoted by obj.
|
||||||
|
// Panic ensues if there is none.
|
||||||
|
//
|
||||||
|
func (prog *Program) declaredFunc(obj *types.Func) *Function {
|
||||||
|
if v := prog.packageLevelValue(obj); v != nil {
|
||||||
|
return v.(*Function)
|
||||||
|
}
|
||||||
|
panic("no concrete method: " + obj.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// needMethodsOf ensures that runtime type information (including the
|
||||||
|
// complete method set) is available for the specified type T and all
|
||||||
|
// its subcomponents.
|
||||||
|
//
|
||||||
|
// needMethodsOf must be called for at least every type that is an
|
||||||
|
// operand of some MakeInterface instruction, and for the type of
|
||||||
|
// every exported package member.
|
||||||
|
//
|
||||||
|
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||||
|
//
|
||||||
|
// Thread-safe. (Called via emitConv from multiple builder goroutines.)
|
||||||
|
//
|
||||||
|
// TODO(adonovan): make this faster. It accounts for 20% of SSA build time.
|
||||||
|
//
|
||||||
|
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||||
|
//
|
||||||
|
func (prog *Program) needMethodsOf(T types.Type) {
|
||||||
|
prog.methodsMu.Lock()
|
||||||
|
prog.needMethods(T, false)
|
||||||
|
prog.methodsMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||||
|
// Recursive case: skip => don't create methods for T.
|
||||||
|
//
|
||||||
|
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||||
|
//
|
||||||
|
func (prog *Program) needMethods(T types.Type, skip bool) {
|
||||||
|
// Each package maintains its own set of types it has visited.
|
||||||
|
if prevSkip, ok := prog.runtimeTypes.At(T).(bool); ok {
|
||||||
|
// needMethods(T) was previously called
|
||||||
|
if !prevSkip || skip {
|
||||||
|
return // already seen, with same or false 'skip' value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prog.runtimeTypes.Set(T, skip)
|
||||||
|
|
||||||
|
tmset := prog.MethodSets.MethodSet(T)
|
||||||
|
|
||||||
|
if !skip && !isInterface(T) && tmset.Len() > 0 {
|
||||||
|
// Create methods of T.
|
||||||
|
mset := prog.createMethodSet(T)
|
||||||
|
if !mset.complete {
|
||||||
|
mset.complete = true
|
||||||
|
n := tmset.Len()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
prog.addMethod(mset, tmset.At(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursion over signatures of each method.
|
||||||
|
for i := 0; i < tmset.Len(); i++ {
|
||||||
|
sig := tmset.At(i).Type().(*types.Signature)
|
||||||
|
prog.needMethods(sig.Params(), false)
|
||||||
|
prog.needMethods(sig.Results(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := T.(type) {
|
||||||
|
case *types.Basic:
|
||||||
|
// nop
|
||||||
|
|
||||||
|
case *types.Interface:
|
||||||
|
// nop---handled by recursion over method set.
|
||||||
|
|
||||||
|
case *types.Pointer:
|
||||||
|
prog.needMethods(t.Elem(), false)
|
||||||
|
|
||||||
|
case *types.Slice:
|
||||||
|
prog.needMethods(t.Elem(), false)
|
||||||
|
|
||||||
|
case *types.Chan:
|
||||||
|
prog.needMethods(t.Elem(), false)
|
||||||
|
|
||||||
|
case *types.Map:
|
||||||
|
prog.needMethods(t.Key(), false)
|
||||||
|
prog.needMethods(t.Elem(), false)
|
||||||
|
|
||||||
|
case *types.Signature:
|
||||||
|
if t.Recv() != nil {
|
||||||
|
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
|
||||||
|
}
|
||||||
|
prog.needMethods(t.Params(), false)
|
||||||
|
prog.needMethods(t.Results(), false)
|
||||||
|
|
||||||
|
case *types.Named:
|
||||||
|
// A pointer-to-named type can be derived from a named
|
||||||
|
// type via reflection. It may have methods too.
|
||||||
|
prog.needMethods(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.
|
||||||
|
prog.needMethods(t.Underlying(), true)
|
||||||
|
|
||||||
|
case *types.Array:
|
||||||
|
prog.needMethods(t.Elem(), false)
|
||||||
|
|
||||||
|
case *types.Struct:
|
||||||
|
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||||
|
prog.needMethods(t.Field(i).Type(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *types.Tuple:
|
||||||
|
for i, n := 0, t.Len(); i < n; i++ {
|
||||||
|
prog.needMethods(t.At(i).Type(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(T)
|
||||||
|
}
|
||||||
|
}
|
100
vendor/honnef.co/go/tools/ssa/mode.go
vendored
Normal file
100
vendor/honnef.co/go/tools/ssa/mode.go
vendored
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// 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 = ssa.BuilderMode(0)
|
||||||
|
// func init() { flag.Var(&mode, "build", ssa.BuilderModeDoc) }
|
||||||
|
//
|
||||||
|
type BuilderMode uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout
|
||||||
|
PrintFunctions // Print function SSA code to stdout
|
||||||
|
LogSource // Log source locations as SSA builder progresses
|
||||||
|
SanityCheckFunctions // Perform sanity checking of function bodies
|
||||||
|
NaiveForm // Build naïve SSA 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
|
||||||
|
)
|
||||||
|
|
||||||
|
const BuilderModeDoc = `Options controlling the SSA builder.
|
||||||
|
The value is a sequence of zero or more of these letters:
|
||||||
|
C perform sanity [C]hecking of the SSA form.
|
||||||
|
D include [D]ebug info for every function.
|
||||||
|
P print [P]ackage inventory.
|
||||||
|
F print [F]unction SSA code.
|
||||||
|
S log [S]ource locations as SSA builder progresses.
|
||||||
|
L build distinct packages seria[L]ly instead of in parallel.
|
||||||
|
N build [N]aive SSA form: don't replace local loads/stores with registers.
|
||||||
|
I build bare [I]nit functions: no init guards or calls to dependent inits.
|
||||||
|
`
|
||||||
|
|
||||||
|
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&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')
|
||||||
|
}
|
||||||
|
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 'S':
|
||||||
|
mode |= LogSource | BuildSerially
|
||||||
|
case 'C':
|
||||||
|
mode |= SanityCheckFunctions
|
||||||
|
case 'N':
|
||||||
|
mode |= NaiveForm
|
||||||
|
case 'L':
|
||||||
|
mode |= BuildSerially
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown BuilderMode option: %q", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*m = mode
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns m.
|
||||||
|
func (m BuilderMode) Get() interface{} { return m }
|
435
vendor/honnef.co/go/tools/ssa/print.go
vendored
Normal file
435
vendor/honnef.co/go/tools/ssa/print.go
vendored
Normal file
|
@ -0,0 +1,435 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// This file implements the String() methods for all Value and
|
||||||
|
// Instruction types.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/types"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"golang.org/x/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 Instruction) string {
|
||||||
|
var from *types.Package
|
||||||
|
if i != nil {
|
||||||
|
from = i.Parent().pkg()
|
||||||
|
}
|
||||||
|
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 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().pkg()
|
||||||
|
return fmt.Sprintf("parameter %s : %s", v.Name(), relType(v.Type(), from))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *FreeVar) String() string {
|
||||||
|
from := v.Parent().pkg()
|
||||||
|
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().pkg()
|
||||||
|
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 := "<nil>" // 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%s", v.Op, relName(v.X, v), commaOk(v.CommaOk))
|
||||||
|
}
|
||||||
|
|
||||||
|
func printConv(prefix string, v, x Value) string {
|
||||||
|
from := v.Parent().pkg()
|
||||||
|
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 *MakeInterface) String() string { return printConv("make", v, v.X) }
|
||||||
|
|
||||||
|
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().pkg()
|
||||||
|
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().pkg()
|
||||||
|
return fmt.Sprintf("make %s %s", relType(v.Type(), from), res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *MakeChan) String() string {
|
||||||
|
from := v.Parent().pkg()
|
||||||
|
return fmt.Sprintf("make %s %s", relType(v.Type(), from), relName(v.Size, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *FieldAddr) String() string {
|
||||||
|
st := deref(v.X.Type()).Underlying().(*types.Struct)
|
||||||
|
// Be robust against a bad index.
|
||||||
|
name := "?"
|
||||||
|
if 0 <= v.Field && v.Field < st.NumFields() {
|
||||||
|
name = st.Field(v.Field).Name()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Field) String() string {
|
||||||
|
st := v.X.Type().Underlying().(*types.Struct)
|
||||||
|
// Be robust against a bad index.
|
||||||
|
name := "?"
|
||||||
|
if 0 <= v.Field && v.Field < st.NumFields() {
|
||||||
|
name = st.Field(v.Field).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 *Lookup) String() string {
|
||||||
|
return fmt.Sprintf("%s[%s]%s", relName(v.X, v), relName(v.Index, v), commaOk(v.CommaOk))
|
||||||
|
}
|
||||||
|
|
||||||
|
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().pkg()
|
||||||
|
return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), relType(v.AssertedType, from))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Extract) String() string {
|
||||||
|
return fmt.Sprintf("extract %s #%d", relName(v.Tuple, v), v.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("jump %d", block)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *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 (s *Defer) String() string {
|
||||||
|
return printCall(&s.Call, "defer ", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 interface{}
|
||||||
|
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(mem.Type().(*types.Pointer).Elem(), from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(buf, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func commaOk(x bool) string {
|
||||||
|
if x {
|
||||||
|
return ",ok"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
523
vendor/honnef.co/go/tools/ssa/sanity.go
vendored
Normal file
523
vendor/honnef.co/go/tools/ssa/sanity.go
vendored
Normal file
|
@ -0,0 +1,523 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// An optional pass for sanity-checking invariants of the SSA representation.
|
||||||
|
// Currently it checks CFG invariants but little at the instruction level.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/types"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sanity struct {
|
||||||
|
reporter io.Writer
|
||||||
|
fn *Function
|
||||||
|
block *BasicBlock
|
||||||
|
instrs map[Instruction]struct{}
|
||||||
|
insane bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanityCheck performs integrity checking of the SSA 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 ...interface{}) {
|
||||||
|
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 ...interface{}) {
|
||||||
|
s.insane = true
|
||||||
|
s.diagnostic("Error", format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sanity) warnf(format string, args ...interface{}) {
|
||||||
|
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:
|
||||||
|
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]
|
||||||
|
if _, ok := prev.(*Phi); !ok {
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *Alloc:
|
||||||
|
if !instr.Heap {
|
||||||
|
found := false
|
||||||
|
for _, l := range s.fn.Locals {
|
||||||
|
if l == instr {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
s.errorf("local alloc %s = %s does not appear in Function.Locals", instr.Name(), instr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *BinOp:
|
||||||
|
case *Call:
|
||||||
|
case *ChangeInterface:
|
||||||
|
case *ChangeType:
|
||||||
|
case *Convert:
|
||||||
|
if _, ok := instr.X.Type().Underlying().(*types.Basic); !ok {
|
||||||
|
if _, ok := instr.Type().Underlying().(*types.Basic); !ok {
|
||||||
|
s.errorf("convert %s -> %s: at least one type must be basic", instr.X.Type(), instr.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *Defer:
|
||||||
|
case *Extract:
|
||||||
|
case *Field:
|
||||||
|
case *FieldAddr:
|
||||||
|
case *Go:
|
||||||
|
case *Index:
|
||||||
|
case *IndexAddr:
|
||||||
|
case *Lookup:
|
||||||
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
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:
|
||||||
|
case *Range:
|
||||||
|
case *RunDefers:
|
||||||
|
case *Select:
|
||||||
|
case *Send:
|
||||||
|
case *Slice:
|
||||||
|
case *Store:
|
||||||
|
case *TypeAssert:
|
||||||
|
case *UnOp:
|
||||||
|
case *DebugRef:
|
||||||
|
case *BlankStore:
|
||||||
|
case *Sigma:
|
||||||
|
// TODO(adonovan): implement checks.
|
||||||
|
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 == tRangeIter {
|
||||||
|
// not a proper type; ignore.
|
||||||
|
} else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
found := false
|
||||||
|
for _, bb := range a.Succs {
|
||||||
|
if bb == b {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
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 {
|
||||||
|
found := false
|
||||||
|
for _, bb := range c.Preds {
|
||||||
|
if bb == b {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
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().(*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 {
|
||||||
|
s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sanity) checkFunction(fn *Function) bool {
|
||||||
|
// TODO(adonovan): check Function invariants:
|
||||||
|
// - check params match signature
|
||||||
|
// - check transient fields are nil
|
||||||
|
// - warn if any fn.Locals do not appear among block instructions.
|
||||||
|
s.fn = fn
|
||||||
|
if fn.Prog == nil {
|
||||||
|
s.errorf("nil Prog")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn.String() // must not crash
|
||||||
|
fn.RelString(fn.pkg()) // 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, "wrapper ") ||
|
||||||
|
strings.HasPrefix(fn.Synthetic, "bound ") ||
|
||||||
|
strings.HasPrefix(fn.Synthetic, "thunk ") ||
|
||||||
|
strings.HasSuffix(fn.name, "Error") {
|
||||||
|
// ok
|
||||||
|
} else {
|
||||||
|
s.errorf("nil Pkg")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn {
|
||||||
|
s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn)
|
||||||
|
}
|
||||||
|
for i, l := range fn.Locals {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Build the set of valid referrers.
|
||||||
|
s.instrs = make(map[Instruction]struct{})
|
||||||
|
for _, b := range fn.Blocks {
|
||||||
|
for _, instr := range b.Instrs {
|
||||||
|
s.instrs[instr] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, p := range fn.Params {
|
||||||
|
if p.Parent() != fn {
|
||||||
|
s.errorf("Param %s at index %d has wrong parent", p.Name(), i)
|
||||||
|
}
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
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 ssa.Function ("init#%d").
|
||||||
|
} else {
|
||||||
|
panic(fmt.Sprintf("%s: %T.Object().Name() = %s, want %s",
|
||||||
|
pkg.Pkg.Path(), mem, obj.Name(), name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if obj.Pos() != mem.Pos() {
|
||||||
|
panic(fmt.Sprintf("%s Pos=%d obj.Pos=%d", mem, mem.Pos(), obj.Pos()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
293
vendor/honnef.co/go/tools/ssa/source.go
vendored
Normal file
293
vendor/honnef.co/go/tools/ssa/source.go
vendored
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// 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 SSA 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SSA 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 SSA code for pkg has been built, so it can be
|
||||||
|
// used to quickly reject check inputs that will cause
|
||||||
|
// EnclosingFunction to fail, prior to SSA 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 SSA 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 {
|
||||||
|
return pkg.values[obj].(*Function)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueForExpr returns the SSA 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 ssa.Value given a source position, use
|
||||||
|
// importer.PathEnclosingInterval to locate the ast.Node, then
|
||||||
|
// EnclosingFunction to locate the Function, then ValueForExpr to find
|
||||||
|
// the ssa.Value.)
|
||||||
|
//
|
||||||
|
func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) {
|
||||||
|
if f.debugInfo() { // (opt)
|
||||||
|
e = unparen(e)
|
||||||
|
for _, b := range f.Blocks {
|
||||||
|
for _, instr := range b.Instrs {
|
||||||
|
if ref, ok := instr.(*DebugRef); ok {
|
||||||
|
if ref.Expr == e {
|
||||||
|
return ref.X, ref.IsAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Lookup functions for source-level named entities (types.Objects) ---
|
||||||
|
|
||||||
|
// Package returns the SSA Package corresponding to the specified
|
||||||
|
// type-checker package object.
|
||||||
|
// It returns nil if no such SSA package has been created.
|
||||||
|
//
|
||||||
|
func (prog *Program) Package(obj *types.Package) *Package {
|
||||||
|
return prog.packages[obj]
|
||||||
|
}
|
||||||
|
|
||||||
|
// packageLevelValue returns the package-level value corresponding to
|
||||||
|
// the specified named object, which may be a package-level const
|
||||||
|
// (*Const), var (*Global) or func (*Function) of some package in
|
||||||
|
// prog. It returns nil if the object is not found.
|
||||||
|
//
|
||||||
|
func (prog *Program) packageLevelValue(obj types.Object) Value {
|
||||||
|
if pkg, ok := prog.packages[obj.Pkg()]; ok {
|
||||||
|
return pkg.values[obj]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncValue returns the concrete Function denoted by the source-level
|
||||||
|
// named function obj, or nil if obj denotes an interface method.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): check the invariant that obj.Type() matches the
|
||||||
|
// result's Signature, both in the params/results and in the receiver.
|
||||||
|
//
|
||||||
|
func (prog *Program) FuncValue(obj *types.Func) *Function {
|
||||||
|
fn, _ := prog.packageLevelValue(obj).(*Function)
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstValue returns the SSA 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())
|
||||||
|
}
|
||||||
|
// Package-level named constant?
|
||||||
|
if v := prog.packageLevelValue(obj); v != nil {
|
||||||
|
return v.(*Const)
|
||||||
|
}
|
||||||
|
return NewConst(obj.Val(), obj.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarValue returns the SSA 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 SSA 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 SSA 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; SSA 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?
|
||||||
|
for _, b := range fn.Blocks {
|
||||||
|
for _, instr := range b.Instrs {
|
||||||
|
if dr, ok := instr.(*DebugRef); ok {
|
||||||
|
if dr.Pos() == id.Pos() {
|
||||||
|
return dr.X, dr.IsAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defining ident of package-level var?
|
||||||
|
if v := prog.packageLevelValue(obj); v != nil {
|
||||||
|
return v.(*Global), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return // e.g. debug info not requested, or var optimized away
|
||||||
|
}
|
1745
vendor/honnef.co/go/tools/ssa/ssa.go
vendored
Normal file
1745
vendor/honnef.co/go/tools/ssa/ssa.go
vendored
Normal file
File diff suppressed because it is too large
Load diff
95
vendor/honnef.co/go/tools/ssa/ssautil/load.go
vendored
Normal file
95
vendor/honnef.co/go/tools/ssa/ssautil/load.go
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
// 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 ssautil
|
||||||
|
|
||||||
|
// This file defines utility functions for constructing programs in SSA form.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
|
"honnef.co/go/tools/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateProgram returns a new program in SSA form, given a program
|
||||||
|
// loaded from source. An SSA package is created for each transitively
|
||||||
|
// error-free package of lprog.
|
||||||
|
//
|
||||||
|
// Code for bodies of functions is not built until Build is called
|
||||||
|
// on the result.
|
||||||
|
//
|
||||||
|
// mode controls diagnostics and checking during SSA construction.
|
||||||
|
//
|
||||||
|
func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program {
|
||||||
|
prog := ssa.NewProgram(lprog.Fset, mode)
|
||||||
|
|
||||||
|
for _, info := range lprog.AllPackages {
|
||||||
|
if info.TransitivelyErrorFree {
|
||||||
|
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prog
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildPackage builds an SSA program with IR for 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; SSA code cannot be built for
|
||||||
|
// those packages. BuildPackage then constructs an ssa.Program with all
|
||||||
|
// dependency packages created, and builds and returns the SSA 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 ../ssa/example_test.go for an example.
|
||||||
|
//
|
||||||
|
func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ssa.BuilderMode) (*ssa.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),
|
||||||
|
}
|
||||||
|
if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
prog := ssa.NewProgram(fset, mode)
|
||||||
|
|
||||||
|
// Create SSA 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())
|
||||||
|
|
||||||
|
// Create and build the primary package.
|
||||||
|
ssapkg := prog.CreatePackage(pkg, files, info, false)
|
||||||
|
ssapkg.Build()
|
||||||
|
return ssapkg, info, nil
|
||||||
|
}
|
234
vendor/honnef.co/go/tools/ssa/ssautil/switch.go
vendored
Normal file
234
vendor/honnef.co/go/tools/ssa/ssautil/switch.go
vendored
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
// 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 ssautil
|
||||||
|
|
||||||
|
// 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/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A ConstCase represents a single constant comparison.
|
||||||
|
// It is part of a Switch.
|
||||||
|
type ConstCase struct {
|
||||||
|
Block *ssa.BasicBlock // block performing the comparison
|
||||||
|
Body *ssa.BasicBlock // body of the case
|
||||||
|
Value *ssa.Const // case comparand
|
||||||
|
}
|
||||||
|
|
||||||
|
// A TypeCase represents a single type assertion.
|
||||||
|
// It is part of a Switch.
|
||||||
|
type TypeCase struct {
|
||||||
|
Block *ssa.BasicBlock // block performing the type assert
|
||||||
|
Body *ssa.BasicBlock // body of the case
|
||||||
|
Type types.Type // case type
|
||||||
|
Binding ssa.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 ssa.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 *ssa.BasicBlock // block containing start of if/else chain
|
||||||
|
X ssa.Value // the switch operand
|
||||||
|
ConstCases []ConstCase // ordered list of constant comparisons
|
||||||
|
TypeCases []TypeCase // ordered list of type assertions
|
||||||
|
Default *ssa.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, 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
|
||||||
|
// ssa.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 ssa.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 SSA representation.)
|
||||||
|
//
|
||||||
|
func Switches(fn *ssa.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[*ssa.BasicBlock]bool) // TODO(adonovan): opt: use ssa.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 *ssa.Const, seen map[*ssa.BasicBlock]bool) {
|
||||||
|
b := sw.Start
|
||||||
|
x := sw.X
|
||||||
|
for x == sw.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]
|
||||||
|
if len(b.Instrs) > 2 {
|
||||||
|
// Block b contains not just 'if x == k',
|
||||||
|
// 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 ssa.Value, T types.Type, seen map[*ssa.BasicBlock]bool) {
|
||||||
|
b := sw.Start
|
||||||
|
x := sw.X
|
||||||
|
for x == sw.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]
|
||||||
|
if len(b.Instrs) > 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 *ssa.BasicBlock) (v ssa.Value, k *ssa.Const) {
|
||||||
|
if n := len(b.Instrs); n >= 2 {
|
||||||
|
if i, ok := b.Instrs[n-1].(*ssa.If); ok {
|
||||||
|
if binop, ok := i.Cond.(*ssa.BinOp); ok && binop.Block() == b && binop.Op == token.EQL {
|
||||||
|
if k, ok := binop.Y.(*ssa.Const); ok {
|
||||||
|
return binop.X, k
|
||||||
|
}
|
||||||
|
if k, ok := binop.X.(*ssa.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 *ssa.BasicBlock) (y, x ssa.Value, T types.Type) {
|
||||||
|
if n := len(b.Instrs); n >= 4 {
|
||||||
|
if i, ok := b.Instrs[n-1].(*ssa.If); ok {
|
||||||
|
if ext1, ok := i.Cond.(*ssa.Extract); ok && ext1.Block() == b && ext1.Index == 1 {
|
||||||
|
if ta, ok := ext1.Tuple.(*ssa.TypeAssert); ok && ta.Block() == b {
|
||||||
|
// hack: relies upon instruction ordering.
|
||||||
|
if ext0, ok := b.Instrs[n-3].(*ssa.Extract); ok {
|
||||||
|
return ext0, ta.X, ta.AssertedType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
79
vendor/honnef.co/go/tools/ssa/ssautil/visit.go
vendored
Normal file
79
vendor/honnef.co/go/tools/ssa/ssautil/visit.go
vendored
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// 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 ssautil // import "honnef.co/go/tools/ssa/ssautil"
|
||||||
|
|
||||||
|
import "honnef.co/go/tools/ssa"
|
||||||
|
|
||||||
|
// This file defines utilities for visiting the SSA representation 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.
|
||||||
|
//
|
||||||
|
func AllFunctions(prog *ssa.Program) map[*ssa.Function]bool {
|
||||||
|
visit := visitor{
|
||||||
|
prog: prog,
|
||||||
|
seen: make(map[*ssa.Function]bool),
|
||||||
|
}
|
||||||
|
visit.program()
|
||||||
|
return visit.seen
|
||||||
|
}
|
||||||
|
|
||||||
|
type visitor struct {
|
||||||
|
prog *ssa.Program
|
||||||
|
seen map[*ssa.Function]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (visit *visitor) program() {
|
||||||
|
for _, pkg := range visit.prog.AllPackages() {
|
||||||
|
for _, mem := range pkg.Members {
|
||||||
|
if fn, ok := mem.(*ssa.Function); ok {
|
||||||
|
visit.function(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, T := range visit.prog.RuntimeTypes() {
|
||||||
|
mset := visit.prog.MethodSets.MethodSet(T)
|
||||||
|
for i, n := 0, mset.Len(); i < n; i++ {
|
||||||
|
visit.function(visit.prog.MethodValue(mset.At(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (visit *visitor) function(fn *ssa.Function) {
|
||||||
|
if !visit.seen[fn] {
|
||||||
|
visit.seen[fn] = true
|
||||||
|
var buf [10]*ssa.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).(*ssa.Function); ok {
|
||||||
|
visit.function(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 []*ssa.Package) []*ssa.Package {
|
||||||
|
var mains []*ssa.Package
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil {
|
||||||
|
mains = append(mains, pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mains
|
||||||
|
}
|
267
vendor/honnef.co/go/tools/ssa/testmain.go
vendored
Normal file
267
vendor/honnef.co/go/tools/ssa/testmain.go
vendored
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// CreateTestMainPackage synthesizes a main package that runs all the
|
||||||
|
// tests of the supplied packages.
|
||||||
|
// It is closely coupled to $GOROOT/src/cmd/go/test.go and $GOROOT/src/testing.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): this file no longer needs to live in the ssa package.
|
||||||
|
// Move it to ssautil.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/types"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FindTests returns the Test, Benchmark, and Example functions
|
||||||
|
// (as defined by "go test") defined in the specified package,
|
||||||
|
// and its TestMain function, if any.
|
||||||
|
func FindTests(pkg *Package) (tests, benchmarks, examples []*Function, main *Function) {
|
||||||
|
prog := pkg.Prog
|
||||||
|
|
||||||
|
// The first two of these may be nil: if the program doesn't import "testing",
|
||||||
|
// it can't contain any tests, but it may yet contain Examples.
|
||||||
|
var testSig *types.Signature // func(*testing.T)
|
||||||
|
var benchmarkSig *types.Signature // func(*testing.B)
|
||||||
|
var exampleSig = types.NewSignature(nil, nil, nil, false) // func()
|
||||||
|
|
||||||
|
// Obtain the types from the parameters of testing.MainStart.
|
||||||
|
if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil {
|
||||||
|
mainStart := testingPkg.Func("MainStart")
|
||||||
|
params := mainStart.Signature.Params()
|
||||||
|
testSig = funcField(params.At(1).Type())
|
||||||
|
benchmarkSig = funcField(params.At(2).Type())
|
||||||
|
|
||||||
|
// Does the package define this function?
|
||||||
|
// func TestMain(*testing.M)
|
||||||
|
if f := pkg.Func("TestMain"); f != nil {
|
||||||
|
sig := f.Type().(*types.Signature)
|
||||||
|
starM := mainStart.Signature.Results().At(0).Type() // *testing.M
|
||||||
|
if sig.Results().Len() == 0 &&
|
||||||
|
sig.Params().Len() == 1 &&
|
||||||
|
types.Identical(sig.Params().At(0).Type(), starM) {
|
||||||
|
main = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(adonovan): use a stable order, e.g. lexical.
|
||||||
|
for _, mem := range pkg.Members {
|
||||||
|
if f, ok := mem.(*Function); ok &&
|
||||||
|
ast.IsExported(f.Name()) &&
|
||||||
|
strings.HasSuffix(prog.Fset.Position(f.Pos()).Filename, "_test.go") {
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case testSig != nil && isTestSig(f, "Test", testSig):
|
||||||
|
tests = append(tests, f)
|
||||||
|
case benchmarkSig != nil && isTestSig(f, "Benchmark", benchmarkSig):
|
||||||
|
benchmarks = append(benchmarks, f)
|
||||||
|
case isTestSig(f, "Example", exampleSig):
|
||||||
|
examples = append(examples, f)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like isTest, but checks the signature too.
|
||||||
|
func isTestSig(f *Function, prefix string, sig *types.Signature) bool {
|
||||||
|
return isTest(f.Name(), prefix) && types.Identical(f.Signature, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the type of one of the three slice parameters of testing.Main,
|
||||||
|
// returns the function type.
|
||||||
|
func funcField(slice types.Type) *types.Signature {
|
||||||
|
return slice.(*types.Slice).Elem().Underlying().(*types.Struct).Field(1).Type().(*types.Signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTest tells whether name looks like a test (or benchmark, according to prefix).
|
||||||
|
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
|
||||||
|
// We don't want TesticularCancer.
|
||||||
|
// Plundered from $GOROOT/src/cmd/go/test.go
|
||||||
|
func isTest(name, prefix string) bool {
|
||||||
|
if !strings.HasPrefix(name, prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(name) == len(prefix) { // "Test" is ok
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return ast.IsExported(name[len(prefix):])
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTestMainPackage creates and returns a synthetic "testmain"
|
||||||
|
// package for the specified package if it defines tests, benchmarks or
|
||||||
|
// executable examples, or nil otherwise. The new package is named
|
||||||
|
// "main" and provides a function named "main" that runs the tests,
|
||||||
|
// similar to the one that would be created by the 'go test' tool.
|
||||||
|
//
|
||||||
|
// Subsequent calls to prog.AllPackages include the new package.
|
||||||
|
// The package pkg must belong to the program prog.
|
||||||
|
func (prog *Program) CreateTestMainPackage(pkg *Package) *Package {
|
||||||
|
if pkg.Prog != prog {
|
||||||
|
log.Fatal("Package does not belong to Program")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template data
|
||||||
|
var data struct {
|
||||||
|
Pkg *Package
|
||||||
|
Tests, Benchmarks, Examples []*Function
|
||||||
|
Main *Function
|
||||||
|
Go18 bool
|
||||||
|
}
|
||||||
|
data.Pkg = pkg
|
||||||
|
|
||||||
|
// Enumerate tests.
|
||||||
|
data.Tests, data.Benchmarks, data.Examples, data.Main = FindTests(pkg)
|
||||||
|
if data.Main == nil &&
|
||||||
|
data.Tests == nil && data.Benchmarks == nil && data.Examples == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synthesize source for testmain package.
|
||||||
|
path := pkg.Pkg.Path() + "$testmain"
|
||||||
|
tmpl := testmainTmpl
|
||||||
|
if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil {
|
||||||
|
// In Go 1.8, testing.MainStart's first argument is an interface, not a func.
|
||||||
|
data.Go18 = types.IsInterface(testingPkg.Func("MainStart").Signature.Params().At(0).Type())
|
||||||
|
} else {
|
||||||
|
// The program does not import "testing", but FindTests
|
||||||
|
// returned non-nil, which must mean there were Examples
|
||||||
|
// but no Test, Benchmark, or TestMain functions.
|
||||||
|
|
||||||
|
// We'll simply call them from testmain.main; this will
|
||||||
|
// ensure they don't panic, but will not check any
|
||||||
|
// "Output:" comments.
|
||||||
|
// (We should not execute an Example that has no
|
||||||
|
// "Output:" comment, but it's impossible to tell here.)
|
||||||
|
tmpl = examplesOnlyTmpl
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&buf, data); err != nil {
|
||||||
|
log.Fatalf("internal error expanding template for %s: %v", path, err)
|
||||||
|
}
|
||||||
|
if false { // debugging
|
||||||
|
fmt.Fprintln(os.Stderr, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and type-check the testmain package.
|
||||||
|
f, err := parser.ParseFile(prog.Fset, path+".go", &buf, parser.Mode(0))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("internal error parsing %s: %v", path, err)
|
||||||
|
}
|
||||||
|
conf := types.Config{
|
||||||
|
DisableUnusedImportCheck: true,
|
||||||
|
Importer: importer{pkg},
|
||||||
|
}
|
||||||
|
files := []*ast.File{f}
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
testmainPkg, err := conf.Check(path, prog.Fset, files, info)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("internal error type-checking %s: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and build SSA code.
|
||||||
|
testmain := prog.CreatePackage(testmainPkg, files, info, false)
|
||||||
|
testmain.SetDebugMode(false)
|
||||||
|
testmain.Build()
|
||||||
|
testmain.Func("main").Synthetic = "test main function"
|
||||||
|
testmain.Func("init").Synthetic = "package initializer"
|
||||||
|
return testmain
|
||||||
|
}
|
||||||
|
|
||||||
|
// An implementation of types.Importer for an already loaded SSA program.
|
||||||
|
type importer struct {
|
||||||
|
pkg *Package // package under test; may be non-importable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (imp importer) Import(path string) (*types.Package, error) {
|
||||||
|
if p := imp.pkg.Prog.ImportedPackage(path); p != nil {
|
||||||
|
return p.Pkg, nil
|
||||||
|
}
|
||||||
|
if path == imp.pkg.Pkg.Path() {
|
||||||
|
return imp.pkg.Pkg, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not found") // can't happen
|
||||||
|
}
|
||||||
|
|
||||||
|
var testmainTmpl = template.Must(template.New("testmain").Parse(`
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
import "os"
|
||||||
|
import "testing"
|
||||||
|
import p {{printf "%q" .Pkg.Pkg.Path}}
|
||||||
|
|
||||||
|
{{if .Go18}}
|
||||||
|
type deps struct{}
|
||||||
|
|
||||||
|
func (deps) ImportPath() string { return "" }
|
||||||
|
func (deps) MatchString(pat, str string) (bool, error) { return true, nil }
|
||||||
|
func (deps) StartCPUProfile(io.Writer) error { return nil }
|
||||||
|
func (deps) StartTestLog(io.Writer) {}
|
||||||
|
func (deps) StopCPUProfile() {}
|
||||||
|
func (deps) StopTestLog() error { return nil }
|
||||||
|
func (deps) WriteHeapProfile(io.Writer) error { return nil }
|
||||||
|
func (deps) WriteProfileTo(string, io.Writer, int) error { return nil }
|
||||||
|
|
||||||
|
var match deps
|
||||||
|
{{else}}
|
||||||
|
func match(_, _ string) (bool, error) { return true, nil }
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
tests := []testing.InternalTest{
|
||||||
|
{{range .Tests}}
|
||||||
|
{ {{printf "%q" .Name}}, p.{{.Name}} },
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
benchmarks := []testing.InternalBenchmark{
|
||||||
|
{{range .Benchmarks}}
|
||||||
|
{ {{printf "%q" .Name}}, p.{{.Name}} },
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
examples := []testing.InternalExample{
|
||||||
|
{{range .Examples}}
|
||||||
|
{Name: {{printf "%q" .Name}}, F: p.{{.Name}}},
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
m := testing.MainStart(match, tests, benchmarks, examples)
|
||||||
|
{{with .Main}}
|
||||||
|
p.{{.Name}}(m)
|
||||||
|
{{else}}
|
||||||
|
os.Exit(m.Run())
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
`))
|
||||||
|
|
||||||
|
var examplesOnlyTmpl = template.Must(template.New("examples").Parse(`
|
||||||
|
package main
|
||||||
|
|
||||||
|
import p {{printf "%q" .Pkg.Pkg.Path}}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
{{range .Examples}}
|
||||||
|
p.{{.Name}}()
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
`))
|
119
vendor/honnef.co/go/tools/ssa/util.go
vendored
Normal file
119
vendor/honnef.co/go/tools/ssa/util.go
vendored
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// This file defines a number of miscellaneous utility functions.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
//// AST utilities
|
||||||
|
|
||||||
|
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// isPointer returns true for types whose underlying type is a pointer.
|
||||||
|
func isPointer(typ types.Type) bool {
|
||||||
|
_, ok := typ.Underlying().(*types.Pointer)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInterface(T types.Type) bool { return types.IsInterface(T) }
|
||||||
|
|
||||||
|
// deref returns a pointer's element type; otherwise it returns typ.
|
||||||
|
func deref(typ types.Type) types.Type {
|
||||||
|
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||||
|
return p.Elem()
|
||||||
|
}
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
|
||||||
|
// recvType returns the receiver type of method obj.
|
||||||
|
func recvType(obj *types.Func) types.Type {
|
||||||
|
return obj.Type().(*types.Signature).Recv().Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultType returns the default "typed" type for an "untyped" type;
|
||||||
|
// it returns the incoming type for all other types. The default type
|
||||||
|
// for untyped nil is untyped nil.
|
||||||
|
//
|
||||||
|
// Exported to ssa/interp.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): use go/types.DefaultType after 1.8.
|
||||||
|
//
|
||||||
|
func DefaultType(typ types.Type) types.Type {
|
||||||
|
if t, ok := typ.(*types.Basic); ok {
|
||||||
|
k := t.Kind()
|
||||||
|
switch k {
|
||||||
|
case types.UntypedBool:
|
||||||
|
k = types.Bool
|
||||||
|
case types.UntypedInt:
|
||||||
|
k = types.Int
|
||||||
|
case types.UntypedRune:
|
||||||
|
k = types.Rune
|
||||||
|
case types.UntypedFloat:
|
||||||
|
k = types.Float64
|
||||||
|
case types.UntypedComplex:
|
||||||
|
k = types.Complex128
|
||||||
|
case types.UntypedString:
|
||||||
|
k = types.String
|
||||||
|
}
|
||||||
|
typ = types.Typ[k]
|
||||||
|
}
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 ...interface{}) 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.NewSignature(nil, lenParams, lenResults, false),
|
||||||
|
}
|
||||||
|
}
|
294
vendor/honnef.co/go/tools/ssa/wrappers.go
vendored
Normal file
294
vendor/honnef.co/go/tools/ssa/wrappers.go
vendored
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
// 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 ssa
|
||||||
|
|
||||||
|
// 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 -----------------------------------------------------------
|
||||||
|
|
||||||
|
// makeWrapper 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
|
||||||
|
// - meth.Obj() may denote a concrete or an interface method
|
||||||
|
// - the result may be a thunk or a wrapper.
|
||||||
|
//
|
||||||
|
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||||
|
//
|
||||||
|
func makeWrapper(prog *Program, sel *types.Selection) *Function {
|
||||||
|
obj := sel.Obj().(*types.Func) // the declared function
|
||||||
|
sig := sel.Type().(*types.Signature) // type of this wrapper
|
||||||
|
|
||||||
|
var recv *types.Var // wrapper's receiver or thunk's params[0]
|
||||||
|
name := obj.Name()
|
||||||
|
var description string
|
||||||
|
var start int // first regular param
|
||||||
|
if sel.Kind() == types.MethodExpr {
|
||||||
|
name += "$thunk"
|
||||||
|
description = "thunk"
|
||||||
|
recv = sig.Params().At(0)
|
||||||
|
start = 1
|
||||||
|
} else {
|
||||||
|
description = "wrapper"
|
||||||
|
recv = sig.Recv()
|
||||||
|
}
|
||||||
|
|
||||||
|
description = fmt.Sprintf("%s for %s", description, sel.Obj())
|
||||||
|
if prog.mode&LogSource != 0 {
|
||||||
|
defer logStack("make %s to (%s)", description, recv.Type())()
|
||||||
|
}
|
||||||
|
fn := &Function{
|
||||||
|
name: name,
|
||||||
|
method: sel,
|
||||||
|
object: obj,
|
||||||
|
Signature: sig,
|
||||||
|
Synthetic: description,
|
||||||
|
Prog: prog,
|
||||||
|
pos: obj.Pos(),
|
||||||
|
}
|
||||||
|
fn.startBody()
|
||||||
|
fn.addSpilledParam(recv)
|
||||||
|
createParams(fn, start)
|
||||||
|
|
||||||
|
indices := sel.Index()
|
||||||
|
|
||||||
|
var v Value = fn.Locals[0] // spilled receiver
|
||||||
|
if isPointer(sel.Recv()) {
|
||||||
|
v = emitLoad(fn, v)
|
||||||
|
|
||||||
|
// For simple indirection wrappers, perform an informative nil-check:
|
||||||
|
// "value method (T).f called using nil *T pointer"
|
||||||
|
if len(indices) == 1 && !isPointer(recvType(obj)) {
|
||||||
|
var c Call
|
||||||
|
c.Call.Value = &Builtin{
|
||||||
|
name: "ssa:wrapnilchk",
|
||||||
|
sig: types.NewSignature(nil,
|
||||||
|
types.NewTuple(anonVar(sel.Recv()), anonVar(tString), anonVar(tString)),
|
||||||
|
types.NewTuple(anonVar(sel.Recv())), false),
|
||||||
|
}
|
||||||
|
c.Call.Args = []Value{
|
||||||
|
v,
|
||||||
|
stringConst(deref(sel.Recv()).String()),
|
||||||
|
stringConst(sel.Obj().Name()),
|
||||||
|
}
|
||||||
|
c.setType(v.Type())
|
||||||
|
v = fn.emit(&c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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])
|
||||||
|
|
||||||
|
// Invariant: v is a pointer, either
|
||||||
|
// value of implicit *C field, or
|
||||||
|
// address of implicit C field.
|
||||||
|
|
||||||
|
var c Call
|
||||||
|
if r := recvType(obj); !isInterface(r) { // concrete method
|
||||||
|
if !isPointer(r) {
|
||||||
|
v = emitLoad(fn, v)
|
||||||
|
}
|
||||||
|
c.Call.Value = prog.declaredFunc(obj)
|
||||||
|
c.Call.Args = append(c.Call.Args, v)
|
||||||
|
} else {
|
||||||
|
c.Call.Method = obj
|
||||||
|
c.Call.Value = emitLoad(fn, v)
|
||||||
|
}
|
||||||
|
for _, arg := range fn.Params[1:] {
|
||||||
|
c.Call.Args = append(c.Call.Args, arg)
|
||||||
|
}
|
||||||
|
emitTailCall(fn, &c)
|
||||||
|
fn.finishBody()
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var last *Parameter
|
||||||
|
tparams := fn.Signature.Params()
|
||||||
|
for i, n := start, tparams.Len(); i < n; i++ {
|
||||||
|
last = fn.addParamObj(tparams.At(i))
|
||||||
|
}
|
||||||
|
if fn.Signature.Variadic() {
|
||||||
|
last.typ = types.NewSlice(last.typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- bounds -----------------------------------------------------------
|
||||||
|
|
||||||
|
// makeBound 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 makeWrapper, makeBound need perform no indirection or field
|
||||||
|
// selections because that can be done before the closure is
|
||||||
|
// constructed.
|
||||||
|
//
|
||||||
|
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||||
|
//
|
||||||
|
func makeBound(prog *Program, obj *types.Func) *Function {
|
||||||
|
prog.methodsMu.Lock()
|
||||||
|
defer prog.methodsMu.Unlock()
|
||||||
|
fn, ok := prog.bounds[obj]
|
||||||
|
if !ok {
|
||||||
|
description := fmt.Sprintf("bound method wrapper for %s", obj)
|
||||||
|
if prog.mode&LogSource != 0 {
|
||||||
|
defer logStack("%s", description)()
|
||||||
|
}
|
||||||
|
fn = &Function{
|
||||||
|
name: obj.Name() + "$bound",
|
||||||
|
object: obj,
|
||||||
|
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
|
||||||
|
Synthetic: description,
|
||||||
|
Prog: prog,
|
||||||
|
pos: obj.Pos(),
|
||||||
|
}
|
||||||
|
|
||||||
|
fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn}
|
||||||
|
fn.FreeVars = []*FreeVar{fv}
|
||||||
|
fn.startBody()
|
||||||
|
createParams(fn, 0)
|
||||||
|
var c Call
|
||||||
|
|
||||||
|
if !isInterface(recvType(obj)) { // concrete
|
||||||
|
c.Call.Value = prog.declaredFunc(obj)
|
||||||
|
c.Call.Args = []Value{fv}
|
||||||
|
} else {
|
||||||
|
c.Call.Value = fv
|
||||||
|
c.Call.Method = obj
|
||||||
|
}
|
||||||
|
for _, arg := range fn.Params {
|
||||||
|
c.Call.Args = append(c.Call.Args, arg)
|
||||||
|
}
|
||||||
|
emitTailCall(fn, &c)
|
||||||
|
fn.finishBody()
|
||||||
|
|
||||||
|
prog.bounds[obj] = fn
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- thunks -----------------------------------------------------------
|
||||||
|
|
||||||
|
// makeThunk 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() }
|
||||||
|
//
|
||||||
|
// TODO(adonovan): opt: currently the stub is created even when used
|
||||||
|
// directly in a function call: C.f(i, 0). This is less efficient
|
||||||
|
// than inlining the stub.
|
||||||
|
//
|
||||||
|
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||||
|
//
|
||||||
|
func makeThunk(prog *Program, sel *types.Selection) *Function {
|
||||||
|
if sel.Kind() != types.MethodExpr {
|
||||||
|
panic(sel)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := selectionKey{
|
||||||
|
kind: sel.Kind(),
|
||||||
|
recv: sel.Recv(),
|
||||||
|
obj: sel.Obj(),
|
||||||
|
index: fmt.Sprint(sel.Index()),
|
||||||
|
indirect: sel.Indirect(),
|
||||||
|
}
|
||||||
|
|
||||||
|
prog.methodsMu.Lock()
|
||||||
|
defer prog.methodsMu.Unlock()
|
||||||
|
|
||||||
|
// Canonicalize key.recv to avoid constructing duplicate thunks.
|
||||||
|
canonRecv, ok := prog.canon.At(key.recv).(types.Type)
|
||||||
|
if !ok {
|
||||||
|
canonRecv = key.recv
|
||||||
|
prog.canon.Set(key.recv, canonRecv)
|
||||||
|
}
|
||||||
|
key.recv = canonRecv
|
||||||
|
|
||||||
|
fn, ok := prog.thunks[key]
|
||||||
|
if !ok {
|
||||||
|
fn = makeWrapper(prog, sel)
|
||||||
|
if fn.Signature.Recv() != nil {
|
||||||
|
panic(fn) // unexpected receiver
|
||||||
|
}
|
||||||
|
prog.thunks[key] = fn
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func changeRecv(s *types.Signature, recv *types.Var) *types.Signature {
|
||||||
|
return types.NewSignature(recv, s.Params(), s.Results(), s.Variadic())
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectionKey is like types.Selection but a usable map key.
|
||||||
|
type selectionKey struct {
|
||||||
|
kind types.SelectionKind
|
||||||
|
recv types.Type // canonicalized via Program.canon
|
||||||
|
obj types.Object
|
||||||
|
index string
|
||||||
|
indirect bool
|
||||||
|
}
|
5
vendor/honnef.co/go/tools/ssa/write.go
vendored
Normal file
5
vendor/honnef.co/go/tools/ssa/write.go
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package ssa
|
||||||
|
|
||||||
|
func NewJump(parent *BasicBlock) *Jump {
|
||||||
|
return &Jump{anInstruction{parent}}
|
||||||
|
}
|
1065
vendor/honnef.co/go/tools/unused/unused.go
vendored
Normal file
1065
vendor/honnef.co/go/tools/unused/unused.go
vendored
Normal file
File diff suppressed because it is too large
Load diff
17
vendor/honnef.co/go/tools/version/version.go
vendored
Normal file
17
vendor/honnef.co/go/tools/version/version.go
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Version = "devel"
|
||||||
|
|
||||||
|
func Print() {
|
||||||
|
if Version == "devel" {
|
||||||
|
fmt.Printf("%s (no version)\n", filepath.Base(os.Args[0]))
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s %s\n", filepath.Base(os.Args[0]), Version)
|
||||||
|
}
|
||||||
|
}
|
19
vendor/modules.txt
vendored
19
vendor/modules.txt
vendored
|
@ -72,8 +72,14 @@ github.com/hashicorp/hcl/json/scanner
|
||||||
github.com/hashicorp/hcl/json/token
|
github.com/hashicorp/hcl/json/token
|
||||||
# github.com/imdario/mergo v0.3.6
|
# github.com/imdario/mergo v0.3.6
|
||||||
github.com/imdario/mergo
|
github.com/imdario/mergo
|
||||||
|
# github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb
|
||||||
|
github.com/jgautheron/goconst/cmd/goconst
|
||||||
|
github.com/jgautheron/goconst
|
||||||
# github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970
|
# github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970
|
||||||
github.com/karalabe/xgo
|
github.com/karalabe/xgo
|
||||||
|
# github.com/kisielk/gotool v1.0.0
|
||||||
|
github.com/kisielk/gotool
|
||||||
|
github.com/kisielk/gotool/internal/load
|
||||||
# github.com/labstack/echo v3.3.5+incompatible
|
# github.com/labstack/echo v3.3.5+incompatible
|
||||||
github.com/labstack/echo
|
github.com/labstack/echo
|
||||||
github.com/labstack/echo/middleware
|
github.com/labstack/echo/middleware
|
||||||
|
@ -174,6 +180,7 @@ golang.org/x/text/unicode/bidi
|
||||||
golang.org/x/tools/go/loader
|
golang.org/x/tools/go/loader
|
||||||
golang.org/x/tools/go/ast/astutil
|
golang.org/x/tools/go/ast/astutil
|
||||||
golang.org/x/tools/go/gcexportdata
|
golang.org/x/tools/go/gcexportdata
|
||||||
|
golang.org/x/tools/go/types/typeutil
|
||||||
golang.org/x/tools/go/buildutil
|
golang.org/x/tools/go/buildutil
|
||||||
golang.org/x/tools/go/internal/cgo
|
golang.org/x/tools/go/internal/cgo
|
||||||
golang.org/x/tools/go/internal/gcimporter
|
golang.org/x/tools/go/internal/gcimporter
|
||||||
|
@ -187,3 +194,15 @@ gopkg.in/gomail.v2
|
||||||
gopkg.in/testfixtures.v2
|
gopkg.in/testfixtures.v2
|
||||||
# gopkg.in/yaml.v2 v2.2.2
|
# gopkg.in/yaml.v2 v2.2.2
|
||||||
gopkg.in/yaml.v2
|
gopkg.in/yaml.v2
|
||||||
|
# honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3
|
||||||
|
honnef.co/go/tools/cmd/gosimple
|
||||||
|
honnef.co/go/tools/cmd/unused
|
||||||
|
honnef.co/go/tools/lint/lintutil
|
||||||
|
honnef.co/go/tools/simple
|
||||||
|
honnef.co/go/tools/unused
|
||||||
|
honnef.co/go/tools/lint
|
||||||
|
honnef.co/go/tools/version
|
||||||
|
honnef.co/go/tools/internal/sharedcheck
|
||||||
|
honnef.co/go/tools/lint/lintdsl
|
||||||
|
honnef.co/go/tools/ssa
|
||||||
|
honnef.co/go/tools/ssa/ssautil
|
||||||
|
|
Loading…
Reference in a new issue