Add staticcheck tool
This commit is contained in:
15
Makefile
15
Makefile
@@ -8,14 +8,15 @@ GOVET := go vet
|
||||
GOIMPORTS := go tool goimports
|
||||
DEADCODE := go tool deadcode
|
||||
GOVULNCHECK := go tool govulncheck
|
||||
STATICCHECK := go tool staticcheck
|
||||
BIOME := biome
|
||||
|
||||
BUILDFLAGS := -buildvcs=true
|
||||
PLATFORMS := linux/amd64
|
||||
|
||||
.PHONY: all build build-all buildinfo check clean deadcode deps fmt help \
|
||||
imports install lint run test tidy tools uninstall verify vet \
|
||||
vulncheck $(CMDS)
|
||||
imports install lint run staticcheck test tidy tools uninstall \
|
||||
verify vet vulncheck $(CMDS)
|
||||
|
||||
## Build all binaries (default target)
|
||||
all: build
|
||||
@@ -67,7 +68,7 @@ test:
|
||||
$(GO) test -cover -race ./...
|
||||
|
||||
## Run all linters (fmt, vet, imports, deadcode, vulncheck)
|
||||
lint: fmt vet imports deadcode vulncheck biome
|
||||
lint: fmt vet imports deadcode vulncheck staticcheck biome
|
||||
|
||||
## Format code using go fmt
|
||||
fmt:
|
||||
@@ -75,7 +76,8 @@ fmt:
|
||||
|
||||
## Fix imports and format using goimports
|
||||
imports:
|
||||
$(GOIMPORTS) -w -local "$(MODULE)" .
|
||||
find . -type f -name '*.go' -not -path './vendor/*' | \
|
||||
xargs $(GOIMPORTS) -w -local "$(MODULE)"
|
||||
|
||||
## Run go vet
|
||||
vet:
|
||||
@@ -96,6 +98,10 @@ check: lint
|
||||
tidy:
|
||||
$(GO) mod tidy
|
||||
|
||||
## Run staticcheck
|
||||
staticcheck:
|
||||
$(STATICCHECK) ./...
|
||||
|
||||
## Download dependencies
|
||||
deps:
|
||||
$(GO) mod download
|
||||
@@ -109,6 +115,7 @@ tools:
|
||||
go get -tool golang.org/x/tools/cmd/deadcode@latest
|
||||
go get -tool golang.org/x/tools/cmd/goimports@latest
|
||||
go get -tool golang.org/x/vuln/cmd/govulncheck@latest
|
||||
go get -tool honnef.co/go/tools/cmd/staticcheck@latest
|
||||
|
||||
## Run the specified CMD binary (use ARGS="..." to pass arguments)
|
||||
# trap '' INT prevents colorize-logs from exiting immediately on Ctrl-C,
|
||||
|
||||
4
go.mod
4
go.mod
@@ -6,6 +6,7 @@ tool (
|
||||
golang.org/x/tools/cmd/deadcode
|
||||
golang.org/x/tools/cmd/goimports
|
||||
golang.org/x/vuln/cmd/govulncheck
|
||||
honnef.co/go/tools/cmd/staticcheck
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -16,10 +17,13 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.44.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
golang.org/x/vuln v1.3.0 // indirect
|
||||
honnef.co/go/tools v0.7.0 // indirect
|
||||
)
|
||||
|
||||
6
go.sum
6
go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
|
||||
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -10,6 +12,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM=
|
||||
github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
|
||||
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
@@ -30,3 +34,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
|
||||
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
|
||||
|
||||
2
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
Normal file
2
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/toml.test
|
||||
/toml-test
|
||||
21
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
Normal file
21
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
120
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
Normal file
120
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
||||
reflection interface similar to Go's standard library `json` and `xml` packages.
|
||||
|
||||
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
|
||||
|
||||
Documentation: https://godocs.io/github.com/BurntSushi/toml
|
||||
|
||||
See the [releases page](https://github.com/BurntSushi/toml/releases) for a
|
||||
changelog; this information is also in the git tag annotations (e.g. `git show
|
||||
v0.4.0`).
|
||||
|
||||
This library requires Go 1.18 or newer; add it to your go.mod with:
|
||||
|
||||
% go get github.com/BurntSushi/toml@latest
|
||||
|
||||
It also comes with a TOML validator CLI tool:
|
||||
|
||||
% go install github.com/BurntSushi/toml/cmd/tomlv@latest
|
||||
% tomlv some-toml-file.toml
|
||||
|
||||
### Examples
|
||||
For the simplest example, consider some TOML file as just a list of keys and
|
||||
values:
|
||||
|
||||
```toml
|
||||
Age = 25
|
||||
Cats = [ "Cauchy", "Plato" ]
|
||||
Pi = 3.14
|
||||
Perfection = [ 6, 28, 496, 8128 ]
|
||||
DOB = 1987-07-05T05:45:00Z
|
||||
```
|
||||
|
||||
Which can be decoded with:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Age int
|
||||
Cats []string
|
||||
Pi float64
|
||||
Perfection []int
|
||||
DOB time.Time
|
||||
}
|
||||
|
||||
var conf Config
|
||||
_, err := toml.Decode(tomlData, &conf)
|
||||
```
|
||||
|
||||
You can also use struct tags if your struct field name doesn't map to a TOML key
|
||||
value directly:
|
||||
|
||||
```toml
|
||||
some_key_NAME = "wat"
|
||||
```
|
||||
|
||||
```go
|
||||
type TOML struct {
|
||||
ObscureKey string `toml:"some_key_NAME"`
|
||||
}
|
||||
```
|
||||
|
||||
Beware that like other decoders **only exported fields** are considered when
|
||||
encoding and decoding; private fields are silently ignored.
|
||||
|
||||
### Using the `Marshaler` and `encoding.TextUnmarshaler` interfaces
|
||||
Here's an example that automatically parses values in a `mail.Address`:
|
||||
|
||||
```toml
|
||||
contacts = [
|
||||
"Donald Duck <donald@duckburg.com>",
|
||||
"Scrooge McDuck <scrooge@duckburg.com>",
|
||||
]
|
||||
```
|
||||
|
||||
Can be decoded with:
|
||||
|
||||
```go
|
||||
// Create address type which satisfies the encoding.TextUnmarshaler interface.
|
||||
type address struct {
|
||||
*mail.Address
|
||||
}
|
||||
|
||||
func (a *address) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
a.Address, err = mail.ParseAddress(string(text))
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode it.
|
||||
func decode() {
|
||||
blob := `
|
||||
contacts = [
|
||||
"Donald Duck <donald@duckburg.com>",
|
||||
"Scrooge McDuck <scrooge@duckburg.com>",
|
||||
]
|
||||
`
|
||||
|
||||
var contacts struct {
|
||||
Contacts []address
|
||||
}
|
||||
|
||||
_, err := toml.Decode(blob, &contacts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, c := range contacts.Contacts {
|
||||
fmt.Printf("%#v\n", c.Address)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// &mail.Address{Name:"Donald Duck", Address:"donald@duckburg.com"}
|
||||
// &mail.Address{Name:"Scrooge McDuck", Address:"scrooge@duckburg.com"}
|
||||
}
|
||||
```
|
||||
|
||||
To target TOML specifically you can implement `UnmarshalTOML` TOML interface in
|
||||
a similar way.
|
||||
|
||||
### More complex usage
|
||||
See the [`_example/`](/_example) directory for a more complex example.
|
||||
615
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
Normal file
615
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
Normal file
@@ -0,0 +1,615 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||||
// TOML description of themselves.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalTOML(any) error
|
||||
}
|
||||
|
||||
// Unmarshal decodes the contents of data in TOML format into a pointer v.
|
||||
//
|
||||
// See [Decoder] for a description of the decoding process.
|
||||
func Unmarshal(data []byte, v any) error {
|
||||
_, err := NewDecoder(bytes.NewReader(data)).Decode(v)
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode the TOML data in to the pointer v.
|
||||
//
|
||||
// See [Decoder] for a description of the decoding process.
|
||||
func Decode(data string, v any) (MetaData, error) {
|
||||
return NewDecoder(strings.NewReader(data)).Decode(v)
|
||||
}
|
||||
|
||||
// DecodeFile reads the contents of a file and decodes it with [Decode].
|
||||
func DecodeFile(path string, v any) (MetaData, error) {
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
defer fp.Close()
|
||||
return NewDecoder(fp).Decode(v)
|
||||
}
|
||||
|
||||
// DecodeFS reads the contents of a file from [fs.FS] and decodes it with
|
||||
// [Decode].
|
||||
func DecodeFS(fsys fs.FS, path string, v any) (MetaData, error) {
|
||||
fp, err := fsys.Open(path)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
defer fp.Close()
|
||||
return NewDecoder(fp).Decode(v)
|
||||
}
|
||||
|
||||
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
||||
//
|
||||
// This type can be used for any value, which will cause decoding to be delayed.
|
||||
// You can use [PrimitiveDecode] to "manually" decode these values.
|
||||
//
|
||||
// NOTE: The underlying representation of a `Primitive` value is subject to
|
||||
// change. Do not rely on it.
|
||||
//
|
||||
// NOTE: Primitive values are still parsed, so using them will only avoid the
|
||||
// overhead of reflection. They can be useful when you don't know the exact type
|
||||
// of TOML data until runtime.
|
||||
type Primitive struct {
|
||||
undecoded any
|
||||
context Key
|
||||
}
|
||||
|
||||
// The significand precision for float32 and float64 is 24 and 53 bits; this is
|
||||
// the range a natural number can be stored in a float without loss of data.
|
||||
const (
|
||||
maxSafeFloat32Int = 16777215 // 2^24-1
|
||||
maxSafeFloat64Int = int64(9007199254740991) // 2^53-1
|
||||
)
|
||||
|
||||
// Decoder decodes TOML data.
|
||||
//
|
||||
// TOML tables correspond to Go structs or maps; they can be used
|
||||
// interchangeably, but structs offer better type safety.
|
||||
//
|
||||
// TOML table arrays correspond to either a slice of structs or a slice of maps.
|
||||
//
|
||||
// TOML datetimes correspond to [time.Time]. Local datetimes are parsed in the
|
||||
// local timezone.
|
||||
//
|
||||
// [time.Duration] types are treated as nanoseconds if the TOML value is an
|
||||
// integer, or they're parsed with time.ParseDuration() if they're strings.
|
||||
//
|
||||
// All other TOML types (float, string, int, bool and array) correspond to the
|
||||
// obvious Go types.
|
||||
//
|
||||
// An exception to the above rules is if a type implements the TextUnmarshaler
|
||||
// interface, in which case any primitive TOML value (floats, strings, integers,
|
||||
// booleans, datetimes) will be converted to a []byte and given to the value's
|
||||
// UnmarshalText method. See the Unmarshaler example for a demonstration with
|
||||
// email addresses.
|
||||
//
|
||||
// # Key mapping
|
||||
//
|
||||
// TOML keys can map to either keys in a Go map or field names in a Go struct.
|
||||
// The special `toml` struct tag can be used to map TOML keys to struct fields
|
||||
// that don't match the key name exactly (see the example). A case insensitive
|
||||
// match to struct names will be tried if an exact match can't be found.
|
||||
//
|
||||
// The mapping between TOML values and Go values is loose. That is, there may
|
||||
// exist TOML values that cannot be placed into your representation, and there
|
||||
// may be parts of your representation that do not correspond to TOML values.
|
||||
// This loose mapping can be made stricter by using the IsDefined and/or
|
||||
// Undecoded methods on the MetaData returned.
|
||||
//
|
||||
// This decoder does not handle cyclic types. Decode will not terminate if a
|
||||
// cyclic type is passed.
|
||||
type Decoder struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
// NewDecoder creates a new Decoder.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{r: r}
|
||||
}
|
||||
|
||||
var (
|
||||
unmarshalToml = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
unmarshalText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
primitiveType = reflect.TypeOf((*Primitive)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Decode TOML data in to the pointer `v`.
|
||||
func (dec *Decoder) Decode(v any) (MetaData, error) {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
s := "%q"
|
||||
if reflect.TypeOf(v) == nil {
|
||||
s = "%v"
|
||||
}
|
||||
|
||||
return MetaData{}, fmt.Errorf("toml: cannot decode to non-pointer "+s, reflect.TypeOf(v))
|
||||
}
|
||||
if rv.IsNil() {
|
||||
return MetaData{}, fmt.Errorf("toml: cannot decode to nil value of %q", reflect.TypeOf(v))
|
||||
}
|
||||
|
||||
// Check if this is a supported type: struct, map, any, or something that
|
||||
// implements UnmarshalTOML or UnmarshalText.
|
||||
rv = indirect(rv)
|
||||
rt := rv.Type()
|
||||
if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map &&
|
||||
!(rv.Kind() == reflect.Interface && rv.NumMethod() == 0) &&
|
||||
!rt.Implements(unmarshalToml) && !rt.Implements(unmarshalText) {
|
||||
return MetaData{}, fmt.Errorf("toml: cannot decode to type %s", rt)
|
||||
}
|
||||
|
||||
// TODO: parser should read from io.Reader? Or at the very least, make it
|
||||
// read from []byte rather than string
|
||||
data, err := io.ReadAll(dec.r)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
|
||||
p, err := parse(string(data))
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
|
||||
md := MetaData{
|
||||
mapping: p.mapping,
|
||||
keyInfo: p.keyInfo,
|
||||
keys: p.ordered,
|
||||
decoded: make(map[string]struct{}, len(p.ordered)),
|
||||
context: nil,
|
||||
data: data,
|
||||
}
|
||||
return md, md.unify(p.mapping, rv)
|
||||
}
|
||||
|
||||
// PrimitiveDecode is just like the other Decode* functions, except it decodes a
|
||||
// TOML value that has already been parsed. Valid primitive values can *only* be
|
||||
// obtained from values filled by the decoder functions, including this method.
|
||||
// (i.e., v may contain more [Primitive] values.)
|
||||
//
|
||||
// Meta data for primitive values is included in the meta data returned by the
|
||||
// Decode* functions with one exception: keys returned by the Undecoded method
|
||||
// will only reflect keys that were decoded. Namely, any keys hidden behind a
|
||||
// Primitive will be considered undecoded. Executing this method will update the
|
||||
// undecoded keys in the meta data. (See the example.)
|
||||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v any) error {
|
||||
md.context = primValue.context
|
||||
defer func() { md.context = nil }()
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// unify performs a sort of type unification based on the structure of `rv`,
|
||||
// which is the client representation.
|
||||
//
|
||||
// Any type mismatch produces an error. Finding a type that we don't know
|
||||
// how to handle produces an unsupported type error.
|
||||
func (md *MetaData) unify(data any, rv reflect.Value) error {
|
||||
// Special case. Look for a `Primitive` value.
|
||||
// TODO: #76 would make this superfluous after implemented.
|
||||
if rv.Type() == primitiveType {
|
||||
// Save the undecoded data and the key context into the primitive
|
||||
// value.
|
||||
context := make(Key, len(md.context))
|
||||
copy(context, md.context)
|
||||
rv.Set(reflect.ValueOf(Primitive{
|
||||
undecoded: data,
|
||||
context: context,
|
||||
}))
|
||||
return nil
|
||||
}
|
||||
|
||||
rvi := rv.Interface()
|
||||
if v, ok := rvi.(Unmarshaler); ok {
|
||||
err := v.UnmarshalTOML(data)
|
||||
if err != nil {
|
||||
return md.parseErr(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if v, ok := rvi.(encoding.TextUnmarshaler); ok {
|
||||
return md.unifyText(data, v)
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// The behavior here is incorrect whenever a Go type satisfies the
|
||||
// encoding.TextUnmarshaler interface but also corresponds to a TOML hash or
|
||||
// array. In particular, the unmarshaler should only be applied to primitive
|
||||
// TOML values. But at this point, it will be applied to all kinds of values
|
||||
// and produce an incorrect error whenever those values are hashes or arrays
|
||||
// (including arrays of tables).
|
||||
|
||||
k := rv.Kind()
|
||||
|
||||
if k >= reflect.Int && k <= reflect.Uint64 {
|
||||
return md.unifyInt(data, rv)
|
||||
}
|
||||
switch k {
|
||||
case reflect.Struct:
|
||||
return md.unifyStruct(data, rv)
|
||||
case reflect.Map:
|
||||
return md.unifyMap(data, rv)
|
||||
case reflect.Array:
|
||||
return md.unifyArray(data, rv)
|
||||
case reflect.Slice:
|
||||
return md.unifySlice(data, rv)
|
||||
case reflect.String:
|
||||
return md.unifyString(data, rv)
|
||||
case reflect.Bool:
|
||||
return md.unifyBool(data, rv)
|
||||
case reflect.Interface:
|
||||
if rv.NumMethod() > 0 { /// Only empty interfaces are supported.
|
||||
return md.e("unsupported type %s", rv.Type())
|
||||
}
|
||||
return md.unifyAnything(data, rv)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return md.unifyFloat64(data, rv)
|
||||
}
|
||||
return md.e("unsupported type %s", rv.Kind())
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyStruct(mapping any, rv reflect.Value) error {
|
||||
tmap, ok := mapping.(map[string]any)
|
||||
if !ok {
|
||||
if mapping == nil {
|
||||
return nil
|
||||
}
|
||||
return md.e("type mismatch for %s: expected table but found %s", rv.Type().String(), fmtType(mapping))
|
||||
}
|
||||
|
||||
for key, datum := range tmap {
|
||||
var f *field
|
||||
fields := cachedTypeFields(rv.Type())
|
||||
for i := range fields {
|
||||
ff := &fields[i]
|
||||
if ff.name == key {
|
||||
f = ff
|
||||
break
|
||||
}
|
||||
if f == nil && strings.EqualFold(ff.name, key) {
|
||||
f = ff
|
||||
}
|
||||
}
|
||||
if f != nil {
|
||||
subv := rv
|
||||
for _, i := range f.index {
|
||||
subv = indirect(subv.Field(i))
|
||||
}
|
||||
|
||||
if isUnifiable(subv) {
|
||||
md.decoded[md.context.add(key).String()] = struct{}{}
|
||||
md.context = append(md.context, key)
|
||||
|
||||
err := md.unify(datum, subv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
} else if f.name != "" {
|
||||
return md.e("cannot write unexported field %s.%s", rv.Type().String(), f.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyMap(mapping any, rv reflect.Value) error {
|
||||
keyType := rv.Type().Key().Kind()
|
||||
if keyType != reflect.String && keyType != reflect.Interface {
|
||||
return fmt.Errorf("toml: cannot decode to a map with non-string key type (%s in %q)",
|
||||
keyType, rv.Type())
|
||||
}
|
||||
|
||||
tmap, ok := mapping.(map[string]any)
|
||||
if !ok {
|
||||
if tmap == nil {
|
||||
return nil
|
||||
}
|
||||
return md.badtype("map", mapping)
|
||||
}
|
||||
if rv.IsNil() {
|
||||
rv.Set(reflect.MakeMap(rv.Type()))
|
||||
}
|
||||
for k, v := range tmap {
|
||||
md.decoded[md.context.add(k).String()] = struct{}{}
|
||||
md.context = append(md.context, k)
|
||||
|
||||
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
||||
|
||||
err := md.unify(v, indirect(rvval))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
|
||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||
|
||||
switch keyType {
|
||||
case reflect.Interface:
|
||||
rvkey.Set(reflect.ValueOf(k))
|
||||
case reflect.String:
|
||||
rvkey.SetString(k)
|
||||
}
|
||||
|
||||
rv.SetMapIndex(rvkey, rvval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyArray(data any, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return md.badtype("slice", data)
|
||||
}
|
||||
if l := datav.Len(); l != rv.Len() {
|
||||
return md.e("expected array length %d; got TOML array of length %d", rv.Len(), l)
|
||||
}
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySlice(data any, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return md.badtype("slice", data)
|
||||
}
|
||||
n := datav.Len()
|
||||
if rv.IsNil() || rv.Cap() < n {
|
||||
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
||||
}
|
||||
rv.SetLen(n)
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
||||
l := data.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyString(data any, rv reflect.Value) error {
|
||||
_, ok := rv.Interface().(json.Number)
|
||||
if ok {
|
||||
if i, ok := data.(int64); ok {
|
||||
rv.SetString(strconv.FormatInt(i, 10))
|
||||
} else if f, ok := data.(float64); ok {
|
||||
rv.SetString(strconv.FormatFloat(f, 'f', -1, 64))
|
||||
} else {
|
||||
return md.badtype("string", data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if s, ok := data.(string); ok {
|
||||
rv.SetString(s)
|
||||
return nil
|
||||
}
|
||||
return md.badtype("string", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyFloat64(data any, rv reflect.Value) error {
|
||||
rvk := rv.Kind()
|
||||
|
||||
if num, ok := data.(float64); ok {
|
||||
switch rvk {
|
||||
case reflect.Float32:
|
||||
if num < -math.MaxFloat32 || num > math.MaxFloat32 {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
rv.SetFloat(num)
|
||||
default:
|
||||
panic("bug")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if num, ok := data.(int64); ok {
|
||||
if (rvk == reflect.Float32 && (num < -maxSafeFloat32Int || num > maxSafeFloat32Int)) ||
|
||||
(rvk == reflect.Float64 && (num < -maxSafeFloat64Int || num > maxSafeFloat64Int)) {
|
||||
return md.parseErr(errUnsafeFloat{i: num, size: rvk.String()})
|
||||
}
|
||||
rv.SetFloat(float64(num))
|
||||
return nil
|
||||
}
|
||||
|
||||
return md.badtype("float", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyInt(data any, rv reflect.Value) error {
|
||||
_, ok := rv.Interface().(time.Duration)
|
||||
if ok {
|
||||
// Parse as string duration, and fall back to regular integer parsing
|
||||
// (as nanosecond) if this is not a string.
|
||||
if s, ok := data.(string); ok {
|
||||
dur, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return md.parseErr(errParseDuration{s})
|
||||
}
|
||||
rv.SetInt(int64(dur))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
num, ok := data.(int64)
|
||||
if !ok {
|
||||
return md.badtype("integer", data)
|
||||
}
|
||||
|
||||
rvk := rv.Kind()
|
||||
switch {
|
||||
case rvk >= reflect.Int && rvk <= reflect.Int64:
|
||||
if (rvk == reflect.Int8 && (num < math.MinInt8 || num > math.MaxInt8)) ||
|
||||
(rvk == reflect.Int16 && (num < math.MinInt16 || num > math.MaxInt16)) ||
|
||||
(rvk == reflect.Int32 && (num < math.MinInt32 || num > math.MaxInt32)) {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
rv.SetInt(num)
|
||||
case rvk >= reflect.Uint && rvk <= reflect.Uint64:
|
||||
unum := uint64(num)
|
||||
if rvk == reflect.Uint8 && (num < 0 || unum > math.MaxUint8) ||
|
||||
rvk == reflect.Uint16 && (num < 0 || unum > math.MaxUint16) ||
|
||||
rvk == reflect.Uint32 && (num < 0 || unum > math.MaxUint32) {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
rv.SetUint(unum)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyBool(data any, rv reflect.Value) error {
|
||||
if b, ok := data.(bool); ok {
|
||||
rv.SetBool(b)
|
||||
return nil
|
||||
}
|
||||
return md.badtype("boolean", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyAnything(data any, rv reflect.Value) error {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyText(data any, v encoding.TextUnmarshaler) error {
|
||||
var s string
|
||||
switch sdata := data.(type) {
|
||||
case Marshaler:
|
||||
text, err := sdata.MarshalTOML()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s = string(text)
|
||||
case encoding.TextMarshaler:
|
||||
text, err := sdata.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s = string(text)
|
||||
case fmt.Stringer:
|
||||
s = sdata.String()
|
||||
case string:
|
||||
s = sdata
|
||||
case bool:
|
||||
s = fmt.Sprintf("%v", sdata)
|
||||
case int64:
|
||||
s = fmt.Sprintf("%d", sdata)
|
||||
case float64:
|
||||
s = fmt.Sprintf("%f", sdata)
|
||||
default:
|
||||
return md.badtype("primitive (string-like)", data)
|
||||
}
|
||||
if err := v.UnmarshalText([]byte(s)); err != nil {
|
||||
return md.parseErr(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) badtype(dst string, data any) error {
|
||||
return md.e("incompatible types: TOML value has type %s; destination has type %s", fmtType(data), dst)
|
||||
}
|
||||
|
||||
func (md *MetaData) parseErr(err error) error {
|
||||
k := md.context.String()
|
||||
d := string(md.data)
|
||||
return ParseError{
|
||||
Message: err.Error(),
|
||||
err: err,
|
||||
LastKey: k,
|
||||
Position: md.keyInfo[k].pos.withCol(d),
|
||||
Line: md.keyInfo[k].pos.Line,
|
||||
input: d,
|
||||
}
|
||||
}
|
||||
|
||||
func (md *MetaData) e(format string, args ...any) error {
|
||||
f := "toml: "
|
||||
if len(md.context) > 0 {
|
||||
f = fmt.Sprintf("toml: (last key %q): ", md.context)
|
||||
p := md.keyInfo[md.context.String()].pos
|
||||
if p.Line > 0 {
|
||||
f = fmt.Sprintf("toml: line %d (last key %q): ", p.Line, md.context)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf(f+format, args...)
|
||||
}
|
||||
|
||||
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
||||
func rvalue(v any) reflect.Value {
|
||||
return indirect(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
// indirect returns the value pointed to by a pointer.
|
||||
//
|
||||
// Pointers are followed until the value is not a pointer. New values are
|
||||
// allocated for each nil pointer.
|
||||
//
|
||||
// An exception to this rule is if the value satisfies an interface of interest
|
||||
// to us (like encoding.TextUnmarshaler).
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
if v.CanSet() {
|
||||
pv := v.Addr()
|
||||
pvi := pv.Interface()
|
||||
if _, ok := pvi.(encoding.TextUnmarshaler); ok {
|
||||
return pv
|
||||
}
|
||||
if _, ok := pvi.(Unmarshaler); ok {
|
||||
return pv
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
return indirect(reflect.Indirect(v))
|
||||
}
|
||||
|
||||
func isUnifiable(rv reflect.Value) bool {
|
||||
if rv.CanSet() {
|
||||
return true
|
||||
}
|
||||
rvi := rv.Interface()
|
||||
if _, ok := rvi.(encoding.TextUnmarshaler); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rvi.(Unmarshaler); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// fmt %T with "interface {}" replaced with "any", which is far more readable.
|
||||
func fmtType(t any) string {
|
||||
return strings.ReplaceAll(fmt.Sprintf("%T", t), "interface {}", "any")
|
||||
}
|
||||
29
vendor/github.com/BurntSushi/toml/deprecated.go
generated
vendored
Normal file
29
vendor/github.com/BurntSushi/toml/deprecated.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"io"
|
||||
)
|
||||
|
||||
// TextMarshaler is an alias for encoding.TextMarshaler.
|
||||
//
|
||||
// Deprecated: use encoding.TextMarshaler
|
||||
type TextMarshaler encoding.TextMarshaler
|
||||
|
||||
// TextUnmarshaler is an alias for encoding.TextUnmarshaler.
|
||||
//
|
||||
// Deprecated: use encoding.TextUnmarshaler
|
||||
type TextUnmarshaler encoding.TextUnmarshaler
|
||||
|
||||
// DecodeReader is an alias for NewDecoder(r).Decode(v).
|
||||
//
|
||||
// Deprecated: use NewDecoder(reader).Decode(&value).
|
||||
func DecodeReader(r io.Reader, v any) (MetaData, error) { return NewDecoder(r).Decode(v) }
|
||||
|
||||
// PrimitiveDecode is an alias for MetaData.PrimitiveDecode().
|
||||
//
|
||||
// Deprecated: use MetaData.PrimitiveDecode.
|
||||
func PrimitiveDecode(primValue Primitive, v any) error {
|
||||
md := MetaData{decoded: make(map[string]struct{})}
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
8
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
Normal file
8
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// Package toml implements decoding and encoding of TOML files.
|
||||
//
|
||||
// This package supports TOML v1.0.0, as specified at https://toml.io
|
||||
//
|
||||
// The github.com/BurntSushi/toml/cmd/tomlv package implements a TOML validator,
|
||||
// and can be used to verify if TOML document is valid. It can also be used to
|
||||
// print the type of each key.
|
||||
package toml
|
||||
778
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
Normal file
778
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
Normal file
@@ -0,0 +1,778 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml/internal"
|
||||
)
|
||||
|
||||
type tomlEncodeError struct{ error }
|
||||
|
||||
var (
|
||||
errArrayNilElement = errors.New("toml: cannot encode array with nil element")
|
||||
errNonString = errors.New("toml: cannot encode a map with non-string key type")
|
||||
errNoKey = errors.New("toml: top-level values must be Go maps or structs")
|
||||
errAnything = errors.New("") // used in testing
|
||||
)
|
||||
|
||||
var dblQuotedReplacer = strings.NewReplacer(
|
||||
"\"", "\\\"",
|
||||
"\\", "\\\\",
|
||||
"\x00", `\u0000`,
|
||||
"\x01", `\u0001`,
|
||||
"\x02", `\u0002`,
|
||||
"\x03", `\u0003`,
|
||||
"\x04", `\u0004`,
|
||||
"\x05", `\u0005`,
|
||||
"\x06", `\u0006`,
|
||||
"\x07", `\u0007`,
|
||||
"\b", `\b`,
|
||||
"\t", `\t`,
|
||||
"\n", `\n`,
|
||||
"\x0b", `\u000b`,
|
||||
"\f", `\f`,
|
||||
"\r", `\r`,
|
||||
"\x0e", `\u000e`,
|
||||
"\x0f", `\u000f`,
|
||||
"\x10", `\u0010`,
|
||||
"\x11", `\u0011`,
|
||||
"\x12", `\u0012`,
|
||||
"\x13", `\u0013`,
|
||||
"\x14", `\u0014`,
|
||||
"\x15", `\u0015`,
|
||||
"\x16", `\u0016`,
|
||||
"\x17", `\u0017`,
|
||||
"\x18", `\u0018`,
|
||||
"\x19", `\u0019`,
|
||||
"\x1a", `\u001a`,
|
||||
"\x1b", `\u001b`,
|
||||
"\x1c", `\u001c`,
|
||||
"\x1d", `\u001d`,
|
||||
"\x1e", `\u001e`,
|
||||
"\x1f", `\u001f`,
|
||||
"\x7f", `\u007f`,
|
||||
)
|
||||
|
||||
var (
|
||||
marshalToml = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
marshalText = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Marshaler is the interface implemented by types that can marshal themselves
|
||||
// into valid TOML.
|
||||
type Marshaler interface {
|
||||
MarshalTOML() ([]byte, error)
|
||||
}
|
||||
|
||||
// Marshal returns a TOML representation of the Go value.
|
||||
//
|
||||
// See [Encoder] for a description of the encoding process.
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
buff := new(bytes.Buffer)
|
||||
if err := NewEncoder(buff).Encode(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
// Encoder encodes a Go to a TOML document.
|
||||
//
|
||||
// The mapping between Go values and TOML values should be precisely the same as
|
||||
// for [Decode].
|
||||
//
|
||||
// time.Time is encoded as a RFC 3339 string, and time.Duration as its string
|
||||
// representation.
|
||||
//
|
||||
// The [Marshaler] and [encoding.TextMarshaler] interfaces are supported to
|
||||
// encoding the value as custom TOML.
|
||||
//
|
||||
// If you want to write arbitrary binary data then you will need to use
|
||||
// something like base64 since TOML does not have any binary types.
|
||||
//
|
||||
// When encoding TOML hashes (Go maps or structs), keys without any sub-hashes
|
||||
// are encoded first.
|
||||
//
|
||||
// Go maps will be sorted alphabetically by key for deterministic output.
|
||||
//
|
||||
// The toml struct tag can be used to provide the key name; if omitted the
|
||||
// struct field name will be used. If the "omitempty" option is present the
|
||||
// following value will be skipped:
|
||||
//
|
||||
// - arrays, slices, maps, and string with len of 0
|
||||
// - struct with all zero values
|
||||
// - bool false
|
||||
//
|
||||
// If omitzero is given all int and float types with a value of 0 will be
|
||||
// skipped.
|
||||
//
|
||||
// Encoding Go values without a corresponding TOML representation will return an
|
||||
// error. Examples of this includes maps with non-string keys, slices with nil
|
||||
// elements, embedded non-struct types, and nested slices containing maps or
|
||||
// structs. (e.g. [][]map[string]string is not allowed but []map[string]string
|
||||
// is okay, as is []map[string][]string).
|
||||
//
|
||||
// NOTE: only exported keys are encoded due to the use of reflection. Unexported
|
||||
// keys are silently discarded.
|
||||
type Encoder struct {
|
||||
Indent string // string for a single indentation level; default is two spaces.
|
||||
hasWritten bool // written any output to w yet?
|
||||
w *bufio.Writer
|
||||
}
|
||||
|
||||
// NewEncoder create a new Encoder.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{w: bufio.NewWriter(w), Indent: " "}
|
||||
}
|
||||
|
||||
// Encode writes a TOML representation of the Go value to the [Encoder]'s writer.
|
||||
//
|
||||
// An error is returned if the value given cannot be encoded to a valid TOML
|
||||
// document.
|
||||
func (enc *Encoder) Encode(v any) error {
|
||||
rv := eindirect(reflect.ValueOf(v))
|
||||
err := enc.safeEncode(Key([]string{}), rv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return enc.w.Flush()
|
||||
}
|
||||
|
||||
func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if terr, ok := r.(tomlEncodeError); ok {
|
||||
err = terr.error
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
enc.encode(key, rv)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
||||
// If we can marshal the type to text, then we use that. This prevents the
|
||||
// encoder for handling these types as generic structs (or whatever the
|
||||
// underlying type of a TextMarshaler is).
|
||||
switch {
|
||||
case isMarshaler(rv):
|
||||
enc.writeKeyValue(key, rv, false)
|
||||
return
|
||||
case rv.Type() == primitiveType: // TODO: #76 would make this superfluous after implemented.
|
||||
enc.encode(key, reflect.ValueOf(rv.Interface().(Primitive).undecoded))
|
||||
return
|
||||
}
|
||||
|
||||
k := rv.Kind()
|
||||
switch k {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
|
||||
enc.writeKeyValue(key, rv, false)
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
|
||||
enc.eArrayOfTables(key, rv)
|
||||
} else {
|
||||
enc.writeKeyValue(key, rv, false)
|
||||
}
|
||||
case reflect.Interface:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.encode(key, rv.Elem())
|
||||
case reflect.Map:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.eTable(key, rv)
|
||||
case reflect.Ptr:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.encode(key, rv.Elem())
|
||||
case reflect.Struct:
|
||||
enc.eTable(key, rv)
|
||||
default:
|
||||
encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k))
|
||||
}
|
||||
}
|
||||
|
||||
// eElement encodes any value that can be an array element.
|
||||
func (enc *Encoder) eElement(rv reflect.Value) {
|
||||
switch v := rv.Interface().(type) {
|
||||
case time.Time: // Using TextMarshaler adds extra quotes, which we don't want.
|
||||
format := time.RFC3339Nano
|
||||
switch v.Location() {
|
||||
case internal.LocalDatetime:
|
||||
format = "2006-01-02T15:04:05.999999999"
|
||||
case internal.LocalDate:
|
||||
format = "2006-01-02"
|
||||
case internal.LocalTime:
|
||||
format = "15:04:05.999999999"
|
||||
}
|
||||
switch v.Location() {
|
||||
default:
|
||||
enc.wf(v.Format(format))
|
||||
case internal.LocalDatetime, internal.LocalDate, internal.LocalTime:
|
||||
enc.wf(v.In(time.UTC).Format(format))
|
||||
}
|
||||
return
|
||||
case Marshaler:
|
||||
s, err := v.MarshalTOML()
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
if s == nil {
|
||||
encPanic(errors.New("MarshalTOML returned nil and no error"))
|
||||
}
|
||||
enc.w.Write(s)
|
||||
return
|
||||
case encoding.TextMarshaler:
|
||||
s, err := v.MarshalText()
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
if s == nil {
|
||||
encPanic(errors.New("MarshalText returned nil and no error"))
|
||||
}
|
||||
enc.writeQuoted(string(s))
|
||||
return
|
||||
case time.Duration:
|
||||
enc.writeQuoted(v.String())
|
||||
return
|
||||
case json.Number:
|
||||
n, _ := rv.Interface().(json.Number)
|
||||
|
||||
if n == "" { /// Useful zero value.
|
||||
enc.w.WriteByte('0')
|
||||
return
|
||||
} else if v, err := n.Int64(); err == nil {
|
||||
enc.eElement(reflect.ValueOf(v))
|
||||
return
|
||||
} else if v, err := n.Float64(); err == nil {
|
||||
enc.eElement(reflect.ValueOf(v))
|
||||
return
|
||||
}
|
||||
encPanic(fmt.Errorf("unable to convert %q to int64 or float64", n))
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Ptr:
|
||||
enc.eElement(rv.Elem())
|
||||
return
|
||||
case reflect.String:
|
||||
enc.writeQuoted(rv.String())
|
||||
case reflect.Bool:
|
||||
enc.wf(strconv.FormatBool(rv.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
||||
case reflect.Float32:
|
||||
f := rv.Float()
|
||||
if math.IsNaN(f) {
|
||||
if math.Signbit(f) {
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.wf("nan")
|
||||
} else if math.IsInf(f, 0) {
|
||||
if math.Signbit(f) {
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.wf("inf")
|
||||
} else {
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32)))
|
||||
}
|
||||
case reflect.Float64:
|
||||
f := rv.Float()
|
||||
if math.IsNaN(f) {
|
||||
if math.Signbit(f) {
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.wf("nan")
|
||||
} else if math.IsInf(f, 0) {
|
||||
if math.Signbit(f) {
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.wf("inf")
|
||||
} else {
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64)))
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
enc.eArrayOrSliceElement(rv)
|
||||
case reflect.Struct:
|
||||
enc.eStruct(nil, rv, true)
|
||||
case reflect.Map:
|
||||
enc.eMap(nil, rv, true)
|
||||
case reflect.Interface:
|
||||
enc.eElement(rv.Elem())
|
||||
default:
|
||||
encPanic(fmt.Errorf("unexpected type: %s", fmtType(rv.Interface())))
|
||||
}
|
||||
}
|
||||
|
||||
// By the TOML spec, all floats must have a decimal with at least one number on
|
||||
// either side.
|
||||
func floatAddDecimal(fstr string) string {
|
||||
if !strings.Contains(fstr, ".") {
|
||||
return fstr + ".0"
|
||||
}
|
||||
return fstr
|
||||
}
|
||||
|
||||
func (enc *Encoder) writeQuoted(s string) {
|
||||
enc.wf("\"%s\"", dblQuotedReplacer.Replace(s))
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
||||
length := rv.Len()
|
||||
enc.wf("[")
|
||||
for i := 0; i < length; i++ {
|
||||
elem := eindirect(rv.Index(i))
|
||||
enc.eElement(elem)
|
||||
if i != length-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
}
|
||||
enc.wf("]")
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
||||
if len(key) == 0 {
|
||||
encPanic(errNoKey)
|
||||
}
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
trv := eindirect(rv.Index(i))
|
||||
if isNil(trv) {
|
||||
continue
|
||||
}
|
||||
enc.newline()
|
||||
enc.wf("%s[[%s]]", enc.indentStr(key), key)
|
||||
enc.newline()
|
||||
enc.eMapOrStruct(key, trv, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
||||
if len(key) == 1 {
|
||||
// Output an extra newline between top-level tables.
|
||||
// (The newline isn't written if nothing else has been written though.)
|
||||
enc.newline()
|
||||
}
|
||||
if len(key) > 0 {
|
||||
enc.wf("%s[%s]", enc.indentStr(key), key)
|
||||
enc.newline()
|
||||
}
|
||||
enc.eMapOrStruct(key, rv, false)
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) {
|
||||
switch rv.Kind() {
|
||||
case reflect.Map:
|
||||
enc.eMap(key, rv, inline)
|
||||
case reflect.Struct:
|
||||
enc.eStruct(key, rv, inline)
|
||||
default:
|
||||
// Should never happen?
|
||||
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
|
||||
rt := rv.Type()
|
||||
if rt.Key().Kind() != reflect.String {
|
||||
encPanic(errNonString)
|
||||
}
|
||||
|
||||
// Sort keys so that we have deterministic output. And write keys directly
|
||||
// underneath this key first, before writing sub-structs or sub-maps.
|
||||
var mapKeysDirect, mapKeysSub []string
|
||||
for _, mapKey := range rv.MapKeys() {
|
||||
k := mapKey.String()
|
||||
if typeIsTable(tomlTypeOfGo(eindirect(rv.MapIndex(mapKey)))) {
|
||||
mapKeysSub = append(mapKeysSub, k)
|
||||
} else {
|
||||
mapKeysDirect = append(mapKeysDirect, k)
|
||||
}
|
||||
}
|
||||
|
||||
var writeMapKeys = func(mapKeys []string, trailC bool) {
|
||||
sort.Strings(mapKeys)
|
||||
for i, mapKey := range mapKeys {
|
||||
val := eindirect(rv.MapIndex(reflect.ValueOf(mapKey)))
|
||||
if isNil(val) {
|
||||
continue
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.writeKeyValue(Key{mapKey}, val, true)
|
||||
if trailC || i != len(mapKeys)-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
} else {
|
||||
enc.encode(key.add(mapKey), val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.wf("{")
|
||||
}
|
||||
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
|
||||
writeMapKeys(mapKeysSub, false)
|
||||
if inline {
|
||||
enc.wf("}")
|
||||
}
|
||||
}
|
||||
|
||||
const is32Bit = (32 << (^uint(0) >> 63)) == 32
|
||||
|
||||
func pointerTo(t reflect.Type) reflect.Type {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
return pointerTo(t.Elem())
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
|
||||
// Write keys for fields directly under this key first, because if we write
|
||||
// a field that creates a new table then all keys under it will be in that
|
||||
// table (not the one we're writing here).
|
||||
//
|
||||
// Fields is a [][]int: for fieldsDirect this always has one entry (the
|
||||
// struct index). For fieldsSub it contains two entries: the parent field
|
||||
// index from tv, and the field indexes for the fields of the sub.
|
||||
var (
|
||||
rt = rv.Type()
|
||||
fieldsDirect, fieldsSub [][]int
|
||||
addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
||||
)
|
||||
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
f := rt.Field(i)
|
||||
isEmbed := f.Anonymous && pointerTo(f.Type).Kind() == reflect.Struct
|
||||
if f.PkgPath != "" && !isEmbed { /// Skip unexported fields.
|
||||
continue
|
||||
}
|
||||
opts := getOptions(f.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
|
||||
frv := eindirect(rv.Field(i))
|
||||
|
||||
if is32Bit {
|
||||
// Copy so it works correct on 32bit archs; not clear why this
|
||||
// is needed. See #314, and https://www.reddit.com/r/golang/comments/pnx8v4
|
||||
// This also works fine on 64bit, but 32bit archs are somewhat
|
||||
// rare and this is a wee bit faster.
|
||||
copyStart := make([]int, len(start))
|
||||
copy(copyStart, start)
|
||||
start = copyStart
|
||||
}
|
||||
|
||||
// Treat anonymous struct fields with tag names as though they are
|
||||
// not anonymous, like encoding/json does.
|
||||
//
|
||||
// Non-struct anonymous fields use the normal encoding logic.
|
||||
if isEmbed {
|
||||
if getOptions(f.Tag).name == "" && frv.Kind() == reflect.Struct {
|
||||
addFields(frv.Type(), frv, append(start, f.Index...))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if typeIsTable(tomlTypeOfGo(frv)) {
|
||||
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
||||
} else {
|
||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||
}
|
||||
}
|
||||
}
|
||||
addFields(rt, rv, nil)
|
||||
|
||||
writeFields := func(fields [][]int) {
|
||||
for _, fieldIndex := range fields {
|
||||
fieldType := rt.FieldByIndex(fieldIndex)
|
||||
fieldVal := rv.FieldByIndex(fieldIndex)
|
||||
|
||||
opts := getOptions(fieldType.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
if opts.omitempty && isEmpty(fieldVal) {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldVal = eindirect(fieldVal)
|
||||
|
||||
if isNil(fieldVal) { /// Don't write anything for nil fields.
|
||||
continue
|
||||
}
|
||||
|
||||
keyName := fieldType.Name
|
||||
if opts.name != "" {
|
||||
keyName = opts.name
|
||||
}
|
||||
|
||||
if opts.omitzero && isZero(fieldVal) {
|
||||
continue
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.writeKeyValue(Key{keyName}, fieldVal, true)
|
||||
if fieldIndex[0] != len(fields)-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
} else {
|
||||
enc.encode(key.add(keyName), fieldVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.wf("{")
|
||||
}
|
||||
writeFields(fieldsDirect)
|
||||
writeFields(fieldsSub)
|
||||
if inline {
|
||||
enc.wf("}")
|
||||
}
|
||||
}
|
||||
|
||||
// tomlTypeOfGo returns the TOML type name of the Go value's type.
|
||||
//
|
||||
// It is used to determine whether the types of array elements are mixed (which
|
||||
// is forbidden). If the Go value is nil, then it is illegal for it to be an
|
||||
// array element, and valueIsNil is returned as true.
|
||||
//
|
||||
// The type may be `nil`, which means no concrete TOML type could be found.
|
||||
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if rv.Kind() == reflect.Struct {
|
||||
if rv.Type() == timeType {
|
||||
return tomlDatetime
|
||||
}
|
||||
if isMarshaler(rv) {
|
||||
return tomlString
|
||||
}
|
||||
return tomlHash
|
||||
}
|
||||
|
||||
if isMarshaler(rv) {
|
||||
return tomlString
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Bool:
|
||||
return tomlBool
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64:
|
||||
return tomlInteger
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return tomlFloat
|
||||
case reflect.Array, reflect.Slice:
|
||||
if isTableArray(rv) {
|
||||
return tomlArrayHash
|
||||
}
|
||||
return tomlArray
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return tomlTypeOfGo(rv.Elem())
|
||||
case reflect.String:
|
||||
return tomlString
|
||||
case reflect.Map:
|
||||
return tomlHash
|
||||
default:
|
||||
encPanic(errors.New("unsupported type: " + rv.Kind().String()))
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func isMarshaler(rv reflect.Value) bool {
|
||||
return rv.Type().Implements(marshalText) || rv.Type().Implements(marshalToml)
|
||||
}
|
||||
|
||||
// isTableArray reports if all entries in the array or slice are a table.
|
||||
func isTableArray(arr reflect.Value) bool {
|
||||
if isNil(arr) || !arr.IsValid() || arr.Len() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
ret := true
|
||||
for i := 0; i < arr.Len(); i++ {
|
||||
tt := tomlTypeOfGo(eindirect(arr.Index(i)))
|
||||
// Don't allow nil.
|
||||
if tt == nil {
|
||||
encPanic(errArrayNilElement)
|
||||
}
|
||||
|
||||
if ret && !typeEqual(tomlHash, tt) {
|
||||
ret = false
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type tagOptions struct {
|
||||
skip bool // "-"
|
||||
name string
|
||||
omitempty bool
|
||||
omitzero bool
|
||||
}
|
||||
|
||||
func getOptions(tag reflect.StructTag) tagOptions {
|
||||
t := tag.Get("toml")
|
||||
if t == "-" {
|
||||
return tagOptions{skip: true}
|
||||
}
|
||||
var opts tagOptions
|
||||
parts := strings.Split(t, ",")
|
||||
opts.name = parts[0]
|
||||
for _, s := range parts[1:] {
|
||||
switch s {
|
||||
case "omitempty":
|
||||
opts.omitempty = true
|
||||
case "omitzero":
|
||||
opts.omitzero = true
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func isZero(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return rv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return rv.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return rv.Float() == 0.0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEmpty(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||
return rv.Len() == 0
|
||||
case reflect.Struct:
|
||||
if rv.Type().Comparable() {
|
||||
return reflect.Zero(rv.Type()).Interface() == rv.Interface()
|
||||
}
|
||||
// Need to also check if all the fields are empty, otherwise something
|
||||
// like this with uncomparable types will always return true:
|
||||
//
|
||||
// type a struct{ field b }
|
||||
// type b struct{ s []string }
|
||||
// s := a{field: b{s: []string{"AAA"}}}
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
if !isEmpty(rv.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Bool:
|
||||
return !rv.Bool()
|
||||
case reflect.Ptr:
|
||||
return rv.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (enc *Encoder) newline() {
|
||||
if enc.hasWritten {
|
||||
enc.wf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Write a key/value pair:
|
||||
//
|
||||
// key = <any value>
|
||||
//
|
||||
// This is also used for "k = v" in inline tables; so something like this will
|
||||
// be written in three calls:
|
||||
//
|
||||
// ┌───────────────────┐
|
||||
// │ ┌───┐ ┌────┐│
|
||||
// v v v v vv
|
||||
// key = {k = 1, k2 = 2}
|
||||
func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
|
||||
/// Marshaler used on top-level document; call eElement() to just call
|
||||
/// Marshal{TOML,Text}.
|
||||
if len(key) == 0 {
|
||||
enc.eElement(val)
|
||||
return
|
||||
}
|
||||
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
||||
enc.eElement(val)
|
||||
if !inline {
|
||||
enc.newline()
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) wf(format string, v ...any) {
|
||||
_, err := fmt.Fprintf(enc.w, format, v...)
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
enc.hasWritten = true
|
||||
}
|
||||
|
||||
func (enc *Encoder) indentStr(key Key) string {
|
||||
return strings.Repeat(enc.Indent, len(key)-1)
|
||||
}
|
||||
|
||||
func encPanic(err error) {
|
||||
panic(tomlEncodeError{err})
|
||||
}
|
||||
|
||||
// Resolve any level of pointers to the actual value (e.g. **string → string).
|
||||
func eindirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
|
||||
if isMarshaler(v) {
|
||||
return v
|
||||
}
|
||||
if v.CanAddr() { /// Special case for marshalers; see #358.
|
||||
if pv := v.Addr(); isMarshaler(pv) {
|
||||
return pv
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
return v
|
||||
}
|
||||
|
||||
return eindirect(v.Elem())
|
||||
}
|
||||
|
||||
func isNil(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
347
vendor/github.com/BurntSushi/toml/error.go
generated
vendored
Normal file
347
vendor/github.com/BurntSushi/toml/error.go
generated
vendored
Normal file
@@ -0,0 +1,347 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseError is returned when there is an error parsing the TOML syntax such as
|
||||
// invalid syntax, duplicate keys, etc.
|
||||
//
|
||||
// In addition to the error message itself, you can also print detailed location
|
||||
// information with context by using [ErrorWithPosition]:
|
||||
//
|
||||
// toml: error: Key 'fruit' was already created and cannot be used as an array.
|
||||
//
|
||||
// At line 4, column 2-7:
|
||||
//
|
||||
// 2 | fruit = []
|
||||
// 3 |
|
||||
// 4 | [[fruit]] # Not allowed
|
||||
// ^^^^^
|
||||
//
|
||||
// [ErrorWithUsage] can be used to print the above with some more detailed usage
|
||||
// guidance:
|
||||
//
|
||||
// toml: error: newlines not allowed within inline tables
|
||||
//
|
||||
// At line 1, column 18:
|
||||
//
|
||||
// 1 | x = [{ key = 42 #
|
||||
// ^
|
||||
//
|
||||
// Error help:
|
||||
//
|
||||
// Inline tables must always be on a single line:
|
||||
//
|
||||
// table = {key = 42, second = 43}
|
||||
//
|
||||
// It is invalid to split them over multiple lines like so:
|
||||
//
|
||||
// # INVALID
|
||||
// table = {
|
||||
// key = 42,
|
||||
// second = 43
|
||||
// }
|
||||
//
|
||||
// Use regular for this:
|
||||
//
|
||||
// [table]
|
||||
// key = 42
|
||||
// second = 43
|
||||
type ParseError struct {
|
||||
Message string // Short technical message.
|
||||
Usage string // Longer message with usage guidance; may be blank.
|
||||
Position Position // Position of the error
|
||||
LastKey string // Last parsed key, may be blank.
|
||||
|
||||
// Line the error occurred.
|
||||
//
|
||||
// Deprecated: use [Position].
|
||||
Line int
|
||||
|
||||
err error
|
||||
input string
|
||||
}
|
||||
|
||||
// Position of an error.
|
||||
type Position struct {
|
||||
Line int // Line number, starting at 1.
|
||||
Col int // Error column, starting at 1.
|
||||
Start int // Start of error, as byte offset starting at 0.
|
||||
Len int // Lenght of the error in bytes.
|
||||
}
|
||||
|
||||
func (p Position) withCol(tomlFile string) Position {
|
||||
var (
|
||||
pos int
|
||||
lines = strings.Split(tomlFile, "\n")
|
||||
)
|
||||
for i := range lines {
|
||||
ll := len(lines[i]) + 1 // +1 for the removed newline
|
||||
if pos+ll >= p.Start {
|
||||
p.Col = p.Start - pos + 1
|
||||
if p.Col < 1 { // Should never happen, but just in case.
|
||||
p.Col = 1
|
||||
}
|
||||
break
|
||||
}
|
||||
pos += ll
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (pe ParseError) Error() string {
|
||||
if pe.LastKey == "" {
|
||||
return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, pe.Message)
|
||||
}
|
||||
return fmt.Sprintf("toml: line %d (last key %q): %s",
|
||||
pe.Position.Line, pe.LastKey, pe.Message)
|
||||
}
|
||||
|
||||
// ErrorWithPosition returns the error with detailed location context.
|
||||
//
|
||||
// See the documentation on [ParseError].
|
||||
func (pe ParseError) ErrorWithPosition() string {
|
||||
if pe.input == "" { // Should never happen, but just in case.
|
||||
return pe.Error()
|
||||
}
|
||||
|
||||
// TODO: don't show control characters as literals? This may not show up
|
||||
// well everywhere.
|
||||
|
||||
var (
|
||||
lines = strings.Split(pe.input, "\n")
|
||||
b = new(strings.Builder)
|
||||
)
|
||||
if pe.Position.Len == 1 {
|
||||
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n",
|
||||
pe.Message, pe.Position.Line, pe.Position.Col)
|
||||
} else {
|
||||
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n",
|
||||
pe.Message, pe.Position.Line, pe.Position.Col, pe.Position.Col+pe.Position.Len-1)
|
||||
}
|
||||
if pe.Position.Line > 2 {
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, expandTab(lines[pe.Position.Line-3]))
|
||||
}
|
||||
if pe.Position.Line > 1 {
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, expandTab(lines[pe.Position.Line-2]))
|
||||
}
|
||||
|
||||
/// Expand tabs, so that the ^^^s are at the correct position, but leave
|
||||
/// "column 10-13" intact. Adjusting this to the visual column would be
|
||||
/// better, but we don't know the tabsize of the user in their editor, which
|
||||
/// can be 8, 4, 2, or something else. We can't know. So leaving it as the
|
||||
/// character index is probably the "most correct".
|
||||
expanded := expandTab(lines[pe.Position.Line-1])
|
||||
diff := len(expanded) - len(lines[pe.Position.Line-1])
|
||||
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, expanded)
|
||||
fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", pe.Position.Col-1+diff), strings.Repeat("^", pe.Position.Len))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ErrorWithUsage returns the error with detailed location context and usage
|
||||
// guidance.
|
||||
//
|
||||
// See the documentation on [ParseError].
|
||||
func (pe ParseError) ErrorWithUsage() string {
|
||||
m := pe.ErrorWithPosition()
|
||||
if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
|
||||
lines := strings.Split(strings.TrimSpace(u.Usage()), "\n")
|
||||
for i := range lines {
|
||||
if lines[i] != "" {
|
||||
lines[i] = " " + lines[i]
|
||||
}
|
||||
}
|
||||
return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n"
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func expandTab(s string) string {
|
||||
var (
|
||||
b strings.Builder
|
||||
l int
|
||||
fill = func(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = ' '
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
)
|
||||
b.Grow(len(s))
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '\t':
|
||||
tw := 8 - l%8
|
||||
b.WriteString(fill(tw))
|
||||
l += tw
|
||||
default:
|
||||
b.WriteRune(r)
|
||||
l += 1
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
type (
|
||||
errLexControl struct{ r rune }
|
||||
errLexEscape struct{ r rune }
|
||||
errLexUTF8 struct{ b byte }
|
||||
errParseDate struct{ v string }
|
||||
errLexInlineTableNL struct{}
|
||||
errLexStringNL struct{}
|
||||
errParseRange struct {
|
||||
i any // int or float
|
||||
size string // "int64", "uint16", etc.
|
||||
}
|
||||
errUnsafeFloat struct {
|
||||
i interface{} // float32 or float64
|
||||
size string // "float32" or "float64"
|
||||
}
|
||||
errParseDuration struct{ d string }
|
||||
)
|
||||
|
||||
func (e errLexControl) Error() string {
|
||||
return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r)
|
||||
}
|
||||
func (e errLexControl) Usage() string { return "" }
|
||||
|
||||
func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) }
|
||||
func (e errLexEscape) Usage() string { return usageEscape }
|
||||
func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) }
|
||||
func (e errLexUTF8) Usage() string { return "" }
|
||||
func (e errParseDate) Error() string { return fmt.Sprintf("invalid datetime: %q", e.v) }
|
||||
func (e errParseDate) Usage() string { return usageDate }
|
||||
func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" }
|
||||
func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
|
||||
func (e errLexStringNL) Error() string { return "strings cannot contain newlines" }
|
||||
func (e errLexStringNL) Usage() string { return usageStringNewline }
|
||||
func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
|
||||
func (e errParseRange) Usage() string { return usageIntOverflow }
|
||||
func (e errUnsafeFloat) Error() string {
|
||||
return fmt.Sprintf("%v is out of the safe %s range", e.i, e.size)
|
||||
}
|
||||
func (e errUnsafeFloat) Usage() string { return usageUnsafeFloat }
|
||||
func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
|
||||
func (e errParseDuration) Usage() string { return usageDuration }
|
||||
|
||||
const usageEscape = `
|
||||
A '\' inside a "-delimited string is interpreted as an escape character.
|
||||
|
||||
The following escape sequences are supported:
|
||||
\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX
|
||||
|
||||
To prevent a '\' from being recognized as an escape character, use either:
|
||||
|
||||
- a ' or '''-delimited string; escape characters aren't processed in them; or
|
||||
- write two backslashes to get a single backslash: '\\'.
|
||||
|
||||
If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/'
|
||||
instead of '\' will usually also work: "C:/Users/martin".
|
||||
`
|
||||
|
||||
const usageInlineNewline = `
|
||||
Inline tables must always be on a single line:
|
||||
|
||||
table = {key = 42, second = 43}
|
||||
|
||||
It is invalid to split them over multiple lines like so:
|
||||
|
||||
# INVALID
|
||||
table = {
|
||||
key = 42,
|
||||
second = 43
|
||||
}
|
||||
|
||||
Use regular for this:
|
||||
|
||||
[table]
|
||||
key = 42
|
||||
second = 43
|
||||
`
|
||||
|
||||
const usageStringNewline = `
|
||||
Strings must always be on a single line, and cannot span more than one line:
|
||||
|
||||
# INVALID
|
||||
string = "Hello,
|
||||
world!"
|
||||
|
||||
Instead use """ or ''' to split strings over multiple lines:
|
||||
|
||||
string = """Hello,
|
||||
world!"""
|
||||
`
|
||||
|
||||
const usageIntOverflow = `
|
||||
This number is too large; this may be an error in the TOML, but it can also be a
|
||||
bug in the program that uses too small of an integer.
|
||||
|
||||
The maximum and minimum values are:
|
||||
|
||||
size │ lowest │ highest
|
||||
───────┼────────────────┼──────────────
|
||||
int8 │ -128 │ 127
|
||||
int16 │ -32,768 │ 32,767
|
||||
int32 │ -2,147,483,648 │ 2,147,483,647
|
||||
int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷
|
||||
uint8 │ 0 │ 255
|
||||
uint16 │ 0 │ 65,535
|
||||
uint32 │ 0 │ 4,294,967,295
|
||||
uint64 │ 0 │ 1.8 × 10¹⁸
|
||||
|
||||
int refers to int32 on 32-bit systems and int64 on 64-bit systems.
|
||||
`
|
||||
|
||||
const usageUnsafeFloat = `
|
||||
This number is outside of the "safe" range for floating point numbers; whole
|
||||
(non-fractional) numbers outside the below range can not always be represented
|
||||
accurately in a float, leading to some loss of accuracy.
|
||||
|
||||
Explicitly mark a number as a fractional unit by adding ".0", which will incur
|
||||
some loss of accuracy; for example:
|
||||
|
||||
f = 2_000_000_000.0
|
||||
|
||||
Accuracy ranges:
|
||||
|
||||
float32 = 16,777,215
|
||||
float64 = 9,007,199,254,740,991
|
||||
`
|
||||
|
||||
const usageDuration = `
|
||||
A duration must be as "number<unit>", without any spaces. Valid units are:
|
||||
|
||||
ns nanoseconds (billionth of a second)
|
||||
us, µs microseconds (millionth of a second)
|
||||
ms milliseconds (thousands of a second)
|
||||
s seconds
|
||||
m minutes
|
||||
h hours
|
||||
|
||||
You can combine multiple units; for example "5m10s" for 5 minutes and 10
|
||||
seconds.
|
||||
`
|
||||
|
||||
const usageDate = `
|
||||
A TOML datetime must be in one of the following formats:
|
||||
|
||||
2006-01-02T15:04:05Z07:00 Date and time, with timezone.
|
||||
2006-01-02T15:04:05 Date and time, but without timezone.
|
||||
2006-01-02 Date without a time or timezone.
|
||||
15:04:05 Just a time, without any timezone.
|
||||
|
||||
Seconds may optionally have a fraction, up to nanosecond precision:
|
||||
|
||||
15:04:05.123
|
||||
15:04:05.856018510
|
||||
`
|
||||
|
||||
// TOML 1.1:
|
||||
// The seconds part in times is optional, and may be omitted:
|
||||
// 2006-01-02T15:04Z07:00
|
||||
// 2006-01-02T15:04
|
||||
// 15:04
|
||||
36
vendor/github.com/BurntSushi/toml/internal/tz.go
generated
vendored
Normal file
36
vendor/github.com/BurntSushi/toml/internal/tz.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package internal
|
||||
|
||||
import "time"
|
||||
|
||||
// Timezones used for local datetime, date, and time TOML types.
|
||||
//
|
||||
// The exact way times and dates without a timezone should be interpreted is not
|
||||
// well-defined in the TOML specification and left to the implementation. These
|
||||
// defaults to current local timezone offset of the computer, but this can be
|
||||
// changed by changing these variables before decoding.
|
||||
//
|
||||
// TODO:
|
||||
// Ideally we'd like to offer people the ability to configure the used timezone
|
||||
// by setting Decoder.Timezone and Encoder.Timezone; however, this is a bit
|
||||
// tricky: the reason we use three different variables for this is to support
|
||||
// round-tripping – without these specific TZ names we wouldn't know which
|
||||
// format to use.
|
||||
//
|
||||
// There isn't a good way to encode this right now though, and passing this sort
|
||||
// of information also ties in to various related issues such as string format
|
||||
// encoding, encoding of comments, etc.
|
||||
//
|
||||
// So, for the time being, just put this in internal until we can write a good
|
||||
// comprehensive API for doing all of this.
|
||||
//
|
||||
// The reason they're exported is because they're referred from in e.g.
|
||||
// internal/tag.
|
||||
//
|
||||
// Note that this behaviour is valid according to the TOML spec as the exact
|
||||
// behaviour is left up to implementations.
|
||||
var (
|
||||
localOffset = func() int { _, o := time.Now().Zone(); return o }()
|
||||
LocalDatetime = time.FixedZone("datetime-local", localOffset)
|
||||
LocalDate = time.FixedZone("date-local", localOffset)
|
||||
LocalTime = time.FixedZone("time-local", localOffset)
|
||||
)
|
||||
1287
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
Normal file
1287
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
148
vendor/github.com/BurntSushi/toml/meta.go
generated
vendored
Normal file
148
vendor/github.com/BurntSushi/toml/meta.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MetaData allows access to meta information about TOML data that's not
|
||||
// accessible otherwise.
|
||||
//
|
||||
// It allows checking if a key is defined in the TOML data, whether any keys
|
||||
// were undecoded, and the TOML type of a key.
|
||||
type MetaData struct {
|
||||
context Key // Used only during decoding.
|
||||
|
||||
keyInfo map[string]keyInfo
|
||||
mapping map[string]any
|
||||
keys []Key
|
||||
decoded map[string]struct{}
|
||||
data []byte // Input file; for errors.
|
||||
}
|
||||
|
||||
// IsDefined reports if the key exists in the TOML data.
|
||||
//
|
||||
// The key should be specified hierarchically, for example to access the TOML
|
||||
// key "a.b.c" you would use IsDefined("a", "b", "c"). Keys are case sensitive.
|
||||
//
|
||||
// Returns false for an empty key.
|
||||
func (md *MetaData) IsDefined(key ...string) bool {
|
||||
if len(key) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
hash map[string]any
|
||||
ok bool
|
||||
hashOrVal any = md.mapping
|
||||
)
|
||||
for _, k := range key {
|
||||
if hash, ok = hashOrVal.(map[string]any); !ok {
|
||||
return false
|
||||
}
|
||||
if hashOrVal, ok = hash[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Type returns a string representation of the type of the key specified.
|
||||
//
|
||||
// Type will return the empty string if given an empty key or a key that does
|
||||
// not exist. Keys are case sensitive.
|
||||
func (md *MetaData) Type(key ...string) string {
|
||||
if ki, ok := md.keyInfo[Key(key).String()]; ok {
|
||||
return ki.tomlType.typeString()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Keys returns a slice of every key in the TOML data, including key groups.
|
||||
//
|
||||
// Each key is itself a slice, where the first element is the top of the
|
||||
// hierarchy and the last is the most specific. The list will have the same
|
||||
// order as the keys appeared in the TOML data.
|
||||
//
|
||||
// All keys returned are non-empty.
|
||||
func (md *MetaData) Keys() []Key {
|
||||
return md.keys
|
||||
}
|
||||
|
||||
// Undecoded returns all keys that have not been decoded in the order in which
|
||||
// they appear in the original TOML document.
|
||||
//
|
||||
// This includes keys that haven't been decoded because of a [Primitive] value.
|
||||
// Once the Primitive value is decoded, the keys will be considered decoded.
|
||||
//
|
||||
// Also note that decoding into an empty interface will result in no decoding,
|
||||
// and so no keys will be considered decoded.
|
||||
//
|
||||
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
||||
// that do not have a concrete type in your representation.
|
||||
func (md *MetaData) Undecoded() []Key {
|
||||
undecoded := make([]Key, 0, len(md.keys))
|
||||
for _, key := range md.keys {
|
||||
if _, ok := md.decoded[key.String()]; !ok {
|
||||
undecoded = append(undecoded, key)
|
||||
}
|
||||
}
|
||||
return undecoded
|
||||
}
|
||||
|
||||
// Key represents any TOML key, including key groups. Use [MetaData.Keys] to get
|
||||
// values of this type.
|
||||
type Key []string
|
||||
|
||||
func (k Key) String() string {
|
||||
// This is called quite often, so it's a bit funky to make it faster.
|
||||
var b strings.Builder
|
||||
b.Grow(len(k) * 25)
|
||||
outer:
|
||||
for i, kk := range k {
|
||||
if i > 0 {
|
||||
b.WriteByte('.')
|
||||
}
|
||||
if kk == "" {
|
||||
b.WriteString(`""`)
|
||||
} else {
|
||||
for _, r := range kk {
|
||||
// "Inline" isBareKeyChar
|
||||
if !((r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-') {
|
||||
b.WriteByte('"')
|
||||
b.WriteString(dblQuotedReplacer.Replace(kk))
|
||||
b.WriteByte('"')
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
b.WriteString(kk)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (k Key) maybeQuoted(i int) string {
|
||||
if k[i] == "" {
|
||||
return `""`
|
||||
}
|
||||
for _, r := range k[i] {
|
||||
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-' {
|
||||
continue
|
||||
}
|
||||
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
|
||||
}
|
||||
return k[i]
|
||||
}
|
||||
|
||||
// Like append(), but only increase the cap by 1.
|
||||
func (k Key) add(piece string) Key {
|
||||
if cap(k) > len(k) {
|
||||
return append(k, piece)
|
||||
}
|
||||
newKey := make(Key, len(k)+1)
|
||||
copy(newKey, k)
|
||||
newKey[len(k)] = piece
|
||||
return newKey
|
||||
}
|
||||
|
||||
func (k Key) parent() Key { return k[:len(k)-1] } // all except the last piece.
|
||||
func (k Key) last() string { return k[len(k)-1] } // last piece of this key.
|
||||
846
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
Normal file
846
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
Normal file
@@ -0,0 +1,846 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/BurntSushi/toml/internal"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
lx *lexer
|
||||
context Key // Full key for the current hash in scope.
|
||||
currentKey string // Base key name for everything except hashes.
|
||||
pos Position // Current position in the TOML file.
|
||||
tomlNext bool
|
||||
|
||||
ordered []Key // List of keys in the order that they appear in the TOML data.
|
||||
|
||||
keyInfo map[string]keyInfo // Map keyname → info about the TOML key.
|
||||
mapping map[string]any // Map keyname → key value.
|
||||
implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names").
|
||||
}
|
||||
|
||||
type keyInfo struct {
|
||||
pos Position
|
||||
tomlType tomlType
|
||||
}
|
||||
|
||||
func parse(data string) (p *parser, err error) {
|
||||
_, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if pErr, ok := r.(ParseError); ok {
|
||||
pErr.input = data
|
||||
err = pErr
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
// Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString()
|
||||
// which mangles stuff. UTF-16 BOM isn't strictly valid, but some tools add
|
||||
// it anyway.
|
||||
if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { // UTF-16
|
||||
data = data[2:]
|
||||
//lint:ignore S1017 https://github.com/dominikh/go-tools/issues/1447
|
||||
} else if strings.HasPrefix(data, "\xef\xbb\xbf") { // UTF-8
|
||||
data = data[3:]
|
||||
}
|
||||
|
||||
// Examine first few bytes for NULL bytes; this probably means it's a UTF-16
|
||||
// file (second byte in surrogate pair being NULL). Again, do this here to
|
||||
// avoid having to deal with UTF-8/16 stuff in the lexer.
|
||||
ex := 6
|
||||
if len(data) < 6 {
|
||||
ex = len(data)
|
||||
}
|
||||
if i := strings.IndexRune(data[:ex], 0); i > -1 {
|
||||
return nil, ParseError{
|
||||
Message: "files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8",
|
||||
Position: Position{Line: 1, Col: 1, Start: i, Len: 1},
|
||||
Line: 1,
|
||||
input: data,
|
||||
}
|
||||
}
|
||||
|
||||
p = &parser{
|
||||
keyInfo: make(map[string]keyInfo),
|
||||
mapping: make(map[string]any),
|
||||
lx: lex(data, tomlNext),
|
||||
ordered: make([]Key, 0),
|
||||
implicits: make(map[string]struct{}),
|
||||
tomlNext: tomlNext,
|
||||
}
|
||||
for {
|
||||
item := p.next()
|
||||
if item.typ == itemEOF {
|
||||
break
|
||||
}
|
||||
p.topLevel(item)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *parser) panicErr(it item, err error) {
|
||||
panic(ParseError{
|
||||
Message: err.Error(),
|
||||
err: err,
|
||||
Position: it.pos.withCol(p.lx.input),
|
||||
Line: it.pos.Len,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *parser) panicItemf(it item, format string, v ...any) {
|
||||
panic(ParseError{
|
||||
Message: fmt.Sprintf(format, v...),
|
||||
Position: it.pos.withCol(p.lx.input),
|
||||
Line: it.pos.Len,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *parser) panicf(format string, v ...any) {
|
||||
panic(ParseError{
|
||||
Message: fmt.Sprintf(format, v...),
|
||||
Position: p.pos.withCol(p.lx.input),
|
||||
Line: p.pos.Line,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *parser) next() item {
|
||||
it := p.lx.nextItem()
|
||||
//fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.pos.Line, it.val)
|
||||
if it.typ == itemError {
|
||||
if it.err != nil {
|
||||
panic(ParseError{
|
||||
Message: it.err.Error(),
|
||||
err: it.err,
|
||||
Position: it.pos.withCol(p.lx.input),
|
||||
Line: it.pos.Line,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
p.panicItemf(it, "%s", it.val)
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) nextPos() item {
|
||||
it := p.next()
|
||||
p.pos = it.pos
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) bug(format string, v ...any) {
|
||||
panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
||||
}
|
||||
|
||||
func (p *parser) expect(typ itemType) item {
|
||||
it := p.next()
|
||||
p.assertEqual(typ, it.typ)
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) assertEqual(expected, got itemType) {
|
||||
if expected != got {
|
||||
p.bug("Expected '%s' but got '%s'.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) topLevel(item item) {
|
||||
switch item.typ {
|
||||
case itemCommentStart: // # ..
|
||||
p.expect(itemText)
|
||||
case itemTableStart: // [ .. ]
|
||||
name := p.nextPos()
|
||||
|
||||
var key Key
|
||||
for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() {
|
||||
key = append(key, p.keyString(name))
|
||||
}
|
||||
p.assertEqual(itemTableEnd, name.typ)
|
||||
|
||||
p.addContext(key, false)
|
||||
p.setType("", tomlHash, item.pos)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemArrayTableStart: // [[ .. ]]
|
||||
name := p.nextPos()
|
||||
|
||||
var key Key
|
||||
for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() {
|
||||
key = append(key, p.keyString(name))
|
||||
}
|
||||
p.assertEqual(itemArrayTableEnd, name.typ)
|
||||
|
||||
p.addContext(key, true)
|
||||
p.setType("", tomlArrayHash, item.pos)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemKeyStart: // key = ..
|
||||
outerContext := p.context
|
||||
/// Read all the key parts (e.g. 'a' and 'b' in 'a.b')
|
||||
k := p.nextPos()
|
||||
var key Key
|
||||
for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
|
||||
key = append(key, p.keyString(k))
|
||||
}
|
||||
p.assertEqual(itemKeyEnd, k.typ)
|
||||
|
||||
/// The current key is the last part.
|
||||
p.currentKey = key.last()
|
||||
|
||||
/// All the other parts (if any) are the context; need to set each part
|
||||
/// as implicit.
|
||||
context := key.parent()
|
||||
for i := range context {
|
||||
p.addImplicitContext(append(p.context, context[i:i+1]...))
|
||||
}
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
|
||||
/// Set value.
|
||||
vItem := p.next()
|
||||
val, typ := p.value(vItem, false)
|
||||
p.setValue(p.currentKey, val)
|
||||
p.setType(p.currentKey, typ, vItem.pos)
|
||||
|
||||
/// Remove the context we added (preserving any context from [tbl] lines).
|
||||
p.context = outerContext
|
||||
p.currentKey = ""
|
||||
default:
|
||||
p.bug("Unexpected type at top level: %s", item.typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Gets a string for a key (or part of a key in a table name).
|
||||
func (p *parser) keyString(it item) string {
|
||||
switch it.typ {
|
||||
case itemText:
|
||||
return it.val
|
||||
case itemString, itemStringEsc, itemMultilineString,
|
||||
itemRawString, itemRawMultilineString:
|
||||
s, _ := p.value(it, false)
|
||||
return s.(string)
|
||||
default:
|
||||
p.bug("Unexpected key type: %s", it.typ)
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var datetimeRepl = strings.NewReplacer(
|
||||
"z", "Z",
|
||||
"t", "T",
|
||||
" ", "T")
|
||||
|
||||
// value translates an expected value from the lexer into a Go value wrapped
|
||||
// as an empty interface.
|
||||
func (p *parser) value(it item, parentIsArray bool) (any, tomlType) {
|
||||
switch it.typ {
|
||||
case itemString:
|
||||
return it.val, p.typeOfPrimitive(it)
|
||||
case itemStringEsc:
|
||||
return p.replaceEscapes(it, it.val), p.typeOfPrimitive(it)
|
||||
case itemMultilineString:
|
||||
return p.replaceEscapes(it, p.stripEscapedNewlines(stripFirstNewline(it.val))), p.typeOfPrimitive(it)
|
||||
case itemRawString:
|
||||
return it.val, p.typeOfPrimitive(it)
|
||||
case itemRawMultilineString:
|
||||
return stripFirstNewline(it.val), p.typeOfPrimitive(it)
|
||||
case itemInteger:
|
||||
return p.valueInteger(it)
|
||||
case itemFloat:
|
||||
return p.valueFloat(it)
|
||||
case itemBool:
|
||||
switch it.val {
|
||||
case "true":
|
||||
return true, p.typeOfPrimitive(it)
|
||||
case "false":
|
||||
return false, p.typeOfPrimitive(it)
|
||||
default:
|
||||
p.bug("Expected boolean value, but got '%s'.", it.val)
|
||||
}
|
||||
case itemDatetime:
|
||||
return p.valueDatetime(it)
|
||||
case itemArray:
|
||||
return p.valueArray(it)
|
||||
case itemInlineTableStart:
|
||||
return p.valueInlineTable(it, parentIsArray)
|
||||
default:
|
||||
p.bug("Unexpected value type: %s", it.typ)
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (p *parser) valueInteger(it item) (any, tomlType) {
|
||||
if !numUnderscoresOK(it.val) {
|
||||
p.panicItemf(it, "Invalid integer %q: underscores must be surrounded by digits", it.val)
|
||||
}
|
||||
if numHasLeadingZero(it.val) {
|
||||
p.panicItemf(it, "Invalid integer %q: cannot have leading zeroes", it.val)
|
||||
}
|
||||
|
||||
num, err := strconv.ParseInt(it.val, 0, 64)
|
||||
if err != nil {
|
||||
// Distinguish integer values. Normally, it'd be a bug if the lexer
|
||||
// provides an invalid integer, but it's possible that the number is
|
||||
// out of range of valid values (which the lexer cannot determine).
|
||||
// So mark the former as a bug but the latter as a legitimate user
|
||||
// error.
|
||||
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
|
||||
p.panicErr(it, errParseRange{i: it.val, size: "int64"})
|
||||
} else {
|
||||
p.bug("Expected integer value, but got '%s'.", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
}
|
||||
|
||||
func (p *parser) valueFloat(it item) (any, tomlType) {
|
||||
parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
||||
switch r {
|
||||
case '.', 'e', 'E':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
for _, part := range parts {
|
||||
if !numUnderscoresOK(part) {
|
||||
p.panicItemf(it, "Invalid float %q: underscores must be surrounded by digits", it.val)
|
||||
}
|
||||
}
|
||||
if len(parts) > 0 && numHasLeadingZero(parts[0]) {
|
||||
p.panicItemf(it, "Invalid float %q: cannot have leading zeroes", it.val)
|
||||
}
|
||||
if !numPeriodsOK(it.val) {
|
||||
// As a special case, numbers like '123.' or '1.e2',
|
||||
// which are valid as far as Go/strconv are concerned,
|
||||
// must be rejected because TOML says that a fractional
|
||||
// part consists of '.' followed by 1+ digits.
|
||||
p.panicItemf(it, "Invalid float %q: '.' must be followed by one or more digits", it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
signbit := false
|
||||
if val == "+nan" || val == "-nan" {
|
||||
signbit = val == "-nan"
|
||||
val = "nan"
|
||||
}
|
||||
num, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
|
||||
p.panicErr(it, errParseRange{i: it.val, size: "float64"})
|
||||
} else {
|
||||
p.panicItemf(it, "Invalid float value: %q", it.val)
|
||||
}
|
||||
}
|
||||
if signbit {
|
||||
num = math.Copysign(num, -1)
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
}
|
||||
|
||||
var dtTypes = []struct {
|
||||
fmt string
|
||||
zone *time.Location
|
||||
next bool
|
||||
}{
|
||||
{time.RFC3339Nano, time.Local, false},
|
||||
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false},
|
||||
{"2006-01-02", internal.LocalDate, false},
|
||||
{"15:04:05.999999999", internal.LocalTime, false},
|
||||
|
||||
// tomlNext
|
||||
{"2006-01-02T15:04Z07:00", time.Local, true},
|
||||
{"2006-01-02T15:04", internal.LocalDatetime, true},
|
||||
{"15:04", internal.LocalTime, true},
|
||||
}
|
||||
|
||||
func (p *parser) valueDatetime(it item) (any, tomlType) {
|
||||
it.val = datetimeRepl.Replace(it.val)
|
||||
var (
|
||||
t time.Time
|
||||
ok bool
|
||||
err error
|
||||
)
|
||||
for _, dt := range dtTypes {
|
||||
if dt.next && !p.tomlNext {
|
||||
continue
|
||||
}
|
||||
t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
|
||||
if err == nil {
|
||||
if missingLeadingZero(it.val, dt.fmt) {
|
||||
p.panicErr(it, errParseDate{it.val})
|
||||
}
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
p.panicErr(it, errParseDate{it.val})
|
||||
}
|
||||
return t, p.typeOfPrimitive(it)
|
||||
}
|
||||
|
||||
// Go's time.Parse() will accept numbers without a leading zero; there isn't any
|
||||
// way to require it. https://github.com/golang/go/issues/29911
|
||||
//
|
||||
// Depend on the fact that the separators (- and :) should always be at the same
|
||||
// location.
|
||||
func missingLeadingZero(d, l string) bool {
|
||||
for i, c := range []byte(l) {
|
||||
if c == '.' || c == 'Z' {
|
||||
return false
|
||||
}
|
||||
if (c < '0' || c > '9') && d[i] != c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *parser) valueArray(it item) (any, tomlType) {
|
||||
p.setType(p.currentKey, tomlArray, it.pos)
|
||||
|
||||
var (
|
||||
// Initialize to a non-nil slice to make it consistent with how S = []
|
||||
// decodes into a non-nil slice inside something like struct { S
|
||||
// []string }. See #338
|
||||
array = make([]any, 0, 2)
|
||||
)
|
||||
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
val, typ := p.value(it, true)
|
||||
array = append(array, val)
|
||||
|
||||
// XXX: type isn't used here, we need it to record the accurate type
|
||||
// information.
|
||||
//
|
||||
// Not entirely sure how to best store this; could use "key[0]",
|
||||
// "key[1]" notation, or maybe store it on the Array type?
|
||||
_ = typ
|
||||
}
|
||||
return array, tomlArray
|
||||
}
|
||||
|
||||
func (p *parser) valueInlineTable(it item, parentIsArray bool) (any, tomlType) {
|
||||
var (
|
||||
topHash = make(map[string]any)
|
||||
outerContext = p.context
|
||||
outerKey = p.currentKey
|
||||
)
|
||||
|
||||
p.context = append(p.context, p.currentKey)
|
||||
prevContext := p.context
|
||||
p.currentKey = ""
|
||||
|
||||
p.addImplicit(p.context)
|
||||
p.addContext(p.context, parentIsArray)
|
||||
|
||||
/// Loop over all table key/value pairs.
|
||||
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
/// Read all key parts.
|
||||
k := p.nextPos()
|
||||
var key Key
|
||||
for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
|
||||
key = append(key, p.keyString(k))
|
||||
}
|
||||
p.assertEqual(itemKeyEnd, k.typ)
|
||||
|
||||
/// The current key is the last part.
|
||||
p.currentKey = key.last()
|
||||
|
||||
/// All the other parts (if any) are the context; need to set each part
|
||||
/// as implicit.
|
||||
context := key.parent()
|
||||
for i := range context {
|
||||
p.addImplicitContext(append(p.context, context[i:i+1]...))
|
||||
}
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
|
||||
/// Set the value.
|
||||
val, typ := p.value(p.next(), false)
|
||||
p.setValue(p.currentKey, val)
|
||||
p.setType(p.currentKey, typ, it.pos)
|
||||
|
||||
hash := topHash
|
||||
for _, c := range context {
|
||||
h, ok := hash[c]
|
||||
if !ok {
|
||||
h = make(map[string]any)
|
||||
hash[c] = h
|
||||
}
|
||||
hash, ok = h.(map[string]any)
|
||||
if !ok {
|
||||
p.panicf("%q is not a table", p.context)
|
||||
}
|
||||
}
|
||||
hash[p.currentKey] = val
|
||||
|
||||
/// Restore context.
|
||||
p.context = prevContext
|
||||
}
|
||||
p.context = outerContext
|
||||
p.currentKey = outerKey
|
||||
return topHash, tomlHash
|
||||
}
|
||||
|
||||
// numHasLeadingZero checks if this number has leading zeroes, allowing for '0',
|
||||
// +/- signs, and base prefixes.
|
||||
func numHasLeadingZero(s string) bool {
|
||||
if len(s) > 1 && s[0] == '0' && !(s[1] == 'b' || s[1] == 'o' || s[1] == 'x') { // Allow 0b, 0o, 0x
|
||||
return true
|
||||
}
|
||||
if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// numUnderscoresOK checks whether each underscore in s is surrounded by
|
||||
// characters that are not underscores.
|
||||
func numUnderscoresOK(s string) bool {
|
||||
switch s {
|
||||
case "nan", "+nan", "-nan", "inf", "-inf", "+inf":
|
||||
return true
|
||||
}
|
||||
accept := false
|
||||
for _, r := range s {
|
||||
if r == '_' {
|
||||
if !accept {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isHexis a superset of all the permissable characters surrounding an
|
||||
// underscore.
|
||||
accept = isHex(r)
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
// numPeriodsOK checks whether every period in s is followed by a digit.
|
||||
func numPeriodsOK(s string) bool {
|
||||
period := false
|
||||
for _, r := range s {
|
||||
if period && !isDigit(r) {
|
||||
return false
|
||||
}
|
||||
period = r == '.'
|
||||
}
|
||||
return !period
|
||||
}
|
||||
|
||||
// Set the current context of the parser, where the context is either a hash or
|
||||
// an array of hashes, depending on the value of the `array` parameter.
|
||||
//
|
||||
// Establishing the context also makes sure that the key isn't a duplicate, and
|
||||
// will create implicit hashes automatically.
|
||||
func (p *parser) addContext(key Key, array bool) {
|
||||
/// Always start at the top level and drill down for our context.
|
||||
hashContext := p.mapping
|
||||
keyContext := make(Key, 0, len(key)-1)
|
||||
|
||||
/// We only need implicit hashes for the parents.
|
||||
for _, k := range key.parent() {
|
||||
_, ok := hashContext[k]
|
||||
keyContext = append(keyContext, k)
|
||||
|
||||
// No key? Make an implicit hash and move on.
|
||||
if !ok {
|
||||
p.addImplicit(keyContext)
|
||||
hashContext[k] = make(map[string]any)
|
||||
}
|
||||
|
||||
// If the hash context is actually an array of tables, then set
|
||||
// the hash context to the last element in that array.
|
||||
//
|
||||
// Otherwise, it better be a table, since this MUST be a key group (by
|
||||
// virtue of it not being the last element in a key).
|
||||
switch t := hashContext[k].(type) {
|
||||
case []map[string]any:
|
||||
hashContext = t[len(t)-1]
|
||||
case map[string]any:
|
||||
hashContext = t
|
||||
default:
|
||||
p.panicf("Key '%s' was already created as a hash.", keyContext)
|
||||
}
|
||||
}
|
||||
|
||||
p.context = keyContext
|
||||
if array {
|
||||
// If this is the first element for this array, then allocate a new
|
||||
// list of tables for it.
|
||||
k := key.last()
|
||||
if _, ok := hashContext[k]; !ok {
|
||||
hashContext[k] = make([]map[string]any, 0, 4)
|
||||
}
|
||||
|
||||
// Add a new table. But make sure the key hasn't already been used
|
||||
// for something else.
|
||||
if hash, ok := hashContext[k].([]map[string]any); ok {
|
||||
hashContext[k] = append(hash, make(map[string]any))
|
||||
} else {
|
||||
p.panicf("Key '%s' was already created and cannot be used as an array.", key)
|
||||
}
|
||||
} else {
|
||||
p.setValue(key.last(), make(map[string]any))
|
||||
}
|
||||
p.context = append(p.context, key.last())
|
||||
}
|
||||
|
||||
// setValue sets the given key to the given value in the current context.
|
||||
// It will make sure that the key hasn't already been defined, account for
|
||||
// implicit key groups.
|
||||
func (p *parser) setValue(key string, value any) {
|
||||
var (
|
||||
tmpHash any
|
||||
ok bool
|
||||
hash = p.mapping
|
||||
keyContext = make(Key, 0, len(p.context)+1)
|
||||
)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
if tmpHash, ok = hash[k]; !ok {
|
||||
p.bug("Context for key '%s' has not been established.", keyContext)
|
||||
}
|
||||
switch t := tmpHash.(type) {
|
||||
case []map[string]any:
|
||||
// The context is a table of hashes. Pick the most recent table
|
||||
// defined as the current hash.
|
||||
hash = t[len(t)-1]
|
||||
case map[string]any:
|
||||
hash = t
|
||||
default:
|
||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||
}
|
||||
}
|
||||
keyContext = append(keyContext, key)
|
||||
|
||||
if _, ok := hash[key]; ok {
|
||||
// Normally redefining keys isn't allowed, but the key could have been
|
||||
// defined implicitly and it's allowed to be redefined concretely. (See
|
||||
// the `valid/implicit-and-explicit-after.toml` in toml-test)
|
||||
//
|
||||
// But we have to make sure to stop marking it as an implicit. (So that
|
||||
// another redefinition provokes an error.)
|
||||
//
|
||||
// Note that since it has already been defined (as a hash), we don't
|
||||
// want to overwrite it. So our business is done.
|
||||
if p.isArray(keyContext) {
|
||||
p.removeImplicit(keyContext)
|
||||
hash[key] = value
|
||||
return
|
||||
}
|
||||
if p.isImplicit(keyContext) {
|
||||
p.removeImplicit(keyContext)
|
||||
return
|
||||
}
|
||||
// Otherwise, we have a concrete key trying to override a previous key,
|
||||
// which is *always* wrong.
|
||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||
}
|
||||
|
||||
hash[key] = value
|
||||
}
|
||||
|
||||
// setType sets the type of a particular value at a given key. It should be
|
||||
// called immediately AFTER setValue.
|
||||
//
|
||||
// Note that if `key` is empty, then the type given will be applied to the
|
||||
// current context (which is either a table or an array of tables).
|
||||
func (p *parser) setType(key string, typ tomlType, pos Position) {
|
||||
keyContext := make(Key, 0, len(p.context)+1)
|
||||
keyContext = append(keyContext, p.context...)
|
||||
if len(key) > 0 { // allow type setting for hashes
|
||||
keyContext = append(keyContext, key)
|
||||
}
|
||||
// Special case to make empty keys ("" = 1) work.
|
||||
// Without it it will set "" rather than `""`.
|
||||
// TODO: why is this needed? And why is this only needed here?
|
||||
if len(keyContext) == 0 {
|
||||
keyContext = Key{""}
|
||||
}
|
||||
p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos}
|
||||
}
|
||||
|
||||
// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and
|
||||
// "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly).
|
||||
func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} }
|
||||
func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) }
|
||||
func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok }
|
||||
func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray }
|
||||
func (p *parser) addImplicitContext(key Key) { p.addImplicit(key); p.addContext(key, false) }
|
||||
|
||||
// current returns the full key name of the current context.
|
||||
func (p *parser) current() string {
|
||||
if len(p.currentKey) == 0 {
|
||||
return p.context.String()
|
||||
}
|
||||
if len(p.context) == 0 {
|
||||
return p.currentKey
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", p.context, p.currentKey)
|
||||
}
|
||||
|
||||
func stripFirstNewline(s string) string {
|
||||
if len(s) > 0 && s[0] == '\n' {
|
||||
return s[1:]
|
||||
}
|
||||
if len(s) > 1 && s[0] == '\r' && s[1] == '\n' {
|
||||
return s[2:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// stripEscapedNewlines removes whitespace after line-ending backslashes in
|
||||
// multiline strings.
|
||||
//
|
||||
// A line-ending backslash is an unescaped \ followed only by whitespace until
|
||||
// the next newline. After a line-ending backslash, all whitespace is removed
|
||||
// until the next non-whitespace character.
|
||||
func (p *parser) stripEscapedNewlines(s string) string {
|
||||
var (
|
||||
b strings.Builder
|
||||
i int
|
||||
)
|
||||
b.Grow(len(s))
|
||||
for {
|
||||
ix := strings.Index(s[i:], `\`)
|
||||
if ix < 0 {
|
||||
b.WriteString(s)
|
||||
return b.String()
|
||||
}
|
||||
i += ix
|
||||
|
||||
if len(s) > i+1 && s[i+1] == '\\' {
|
||||
// Escaped backslash.
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
// Scan until the next non-whitespace.
|
||||
j := i + 1
|
||||
whitespaceLoop:
|
||||
for ; j < len(s); j++ {
|
||||
switch s[j] {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
default:
|
||||
break whitespaceLoop
|
||||
}
|
||||
}
|
||||
if j == i+1 {
|
||||
// Not a whitespace escape.
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(s[i:j], "\n") {
|
||||
// This is not a line-ending backslash. (It's a bad escape sequence,
|
||||
// but we can let replaceEscapes catch it.)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
b.WriteString(s[:i])
|
||||
s = s[j:]
|
||||
i = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) replaceEscapes(it item, str string) string {
|
||||
var (
|
||||
b strings.Builder
|
||||
skip = 0
|
||||
)
|
||||
b.Grow(len(str))
|
||||
for i, c := range str {
|
||||
if skip > 0 {
|
||||
skip--
|
||||
continue
|
||||
}
|
||||
if c != '\\' {
|
||||
b.WriteRune(c)
|
||||
continue
|
||||
}
|
||||
|
||||
if i >= len(str) {
|
||||
p.bug("Escape sequence at end of string.")
|
||||
return ""
|
||||
}
|
||||
switch str[i+1] {
|
||||
default:
|
||||
p.bug("Expected valid escape code after \\, but got %q.", str[i+1])
|
||||
case ' ', '\t':
|
||||
p.panicItemf(it, "invalid escape: '\\%c'", str[i+1])
|
||||
case 'b':
|
||||
b.WriteByte(0x08)
|
||||
skip = 1
|
||||
case 't':
|
||||
b.WriteByte(0x09)
|
||||
skip = 1
|
||||
case 'n':
|
||||
b.WriteByte(0x0a)
|
||||
skip = 1
|
||||
case 'f':
|
||||
b.WriteByte(0x0c)
|
||||
skip = 1
|
||||
case 'r':
|
||||
b.WriteByte(0x0d)
|
||||
skip = 1
|
||||
case 'e':
|
||||
if p.tomlNext {
|
||||
b.WriteByte(0x1b)
|
||||
skip = 1
|
||||
}
|
||||
case '"':
|
||||
b.WriteByte(0x22)
|
||||
skip = 1
|
||||
case '\\':
|
||||
b.WriteByte(0x5c)
|
||||
skip = 1
|
||||
// The lexer guarantees the correct number of characters are present;
|
||||
// don't need to check here.
|
||||
case 'x':
|
||||
if p.tomlNext {
|
||||
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4])
|
||||
b.WriteRune(escaped)
|
||||
skip = 3
|
||||
}
|
||||
case 'u':
|
||||
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+6])
|
||||
b.WriteRune(escaped)
|
||||
skip = 5
|
||||
case 'U':
|
||||
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+10])
|
||||
b.WriteRune(escaped)
|
||||
skip = 9
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (p *parser) asciiEscapeToUnicode(it item, s string) rune {
|
||||
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
|
||||
if err != nil {
|
||||
p.bug("Could not parse '%s' as a hexadecimal number, but the lexer claims it's OK: %s", s, err)
|
||||
}
|
||||
if !utf8.ValidRune(rune(hex)) {
|
||||
p.panicItemf(it, "Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||
}
|
||||
return rune(hex)
|
||||
}
|
||||
238
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
Normal file
238
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
package toml
|
||||
|
||||
// Struct field handling is adapted from code in encoding/json:
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the Go distribution.
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A field represents a single field found in a struct.
|
||||
type field struct {
|
||||
name string // the name of the field (`toml` tag included)
|
||||
tag bool // whether field has a `toml` tag
|
||||
index []int // represents the depth of an anonymous field
|
||||
typ reflect.Type // the type of the field
|
||||
}
|
||||
|
||||
// byName sorts field by name, breaking ties with depth,
|
||||
// then breaking ties with "name came from toml tag", then
|
||||
// breaking ties with index sequence.
|
||||
type byName []field
|
||||
|
||||
func (x byName) Len() int { return len(x) }
|
||||
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
func (x byName) Less(i, j int) bool {
|
||||
if x[i].name != x[j].name {
|
||||
return x[i].name < x[j].name
|
||||
}
|
||||
if len(x[i].index) != len(x[j].index) {
|
||||
return len(x[i].index) < len(x[j].index)
|
||||
}
|
||||
if x[i].tag != x[j].tag {
|
||||
return x[i].tag
|
||||
}
|
||||
return byIndex(x).Less(i, j)
|
||||
}
|
||||
|
||||
// byIndex sorts field by index sequence.
|
||||
type byIndex []field
|
||||
|
||||
func (x byIndex) Len() int { return len(x) }
|
||||
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
func (x byIndex) Less(i, j int) bool {
|
||||
for k, xik := range x[i].index {
|
||||
if k >= len(x[j].index) {
|
||||
return false
|
||||
}
|
||||
if xik != x[j].index[k] {
|
||||
return xik < x[j].index[k]
|
||||
}
|
||||
}
|
||||
return len(x[i].index) < len(x[j].index)
|
||||
}
|
||||
|
||||
// typeFields returns a list of fields that TOML should recognize for the given
|
||||
// type. The algorithm is breadth-first search over the set of structs to
|
||||
// include - the top struct and then any reachable anonymous structs.
|
||||
func typeFields(t reflect.Type) []field {
|
||||
// Anonymous fields to explore at the current level and the next.
|
||||
current := []field{}
|
||||
next := []field{{typ: t}}
|
||||
|
||||
// Count of queued names for current level and the next.
|
||||
var count map[reflect.Type]int
|
||||
var nextCount map[reflect.Type]int
|
||||
|
||||
// Types already visited at an earlier level.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
||||
// Fields found.
|
||||
var fields []field
|
||||
|
||||
for len(next) > 0 {
|
||||
current, next = next, current[:0]
|
||||
count, nextCount = nextCount, map[reflect.Type]int{}
|
||||
|
||||
for _, f := range current {
|
||||
if visited[f.typ] {
|
||||
continue
|
||||
}
|
||||
visited[f.typ] = true
|
||||
|
||||
// Scan f.typ for fields to include.
|
||||
for i := 0; i < f.typ.NumField(); i++ {
|
||||
sf := f.typ.Field(i)
|
||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||
continue
|
||||
}
|
||||
opts := getOptions(sf.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
index := make([]int, len(f.index)+1)
|
||||
copy(index, f.index)
|
||||
index[len(f.index)] = i
|
||||
|
||||
ft := sf.Type
|
||||
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
||||
// Follow pointer.
|
||||
ft = ft.Elem()
|
||||
}
|
||||
|
||||
// Record found field and index sequence.
|
||||
if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
||||
tagged := opts.name != ""
|
||||
name := opts.name
|
||||
if name == "" {
|
||||
name = sf.Name
|
||||
}
|
||||
fields = append(fields, field{name, tagged, index, ft})
|
||||
if count[f.typ] > 1 {
|
||||
// If there were multiple instances, add a second,
|
||||
// so that the annihilation code will see a duplicate.
|
||||
// It only cares about the distinction between 1 or 2,
|
||||
// so don't bother generating any more copies.
|
||||
fields = append(fields, fields[len(fields)-1])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Record new anonymous struct to explore in next round.
|
||||
nextCount[ft]++
|
||||
if nextCount[ft] == 1 {
|
||||
f := field{name: ft.Name(), index: index, typ: ft}
|
||||
next = append(next, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(byName(fields))
|
||||
|
||||
// Delete all fields that are hidden by the Go rules for embedded fields,
|
||||
// except that fields with TOML tags are promoted.
|
||||
|
||||
// The fields are sorted in primary order of name, secondary order
|
||||
// of field index length. Loop over names; for each name, delete
|
||||
// hidden fields by choosing the one dominant field that survives.
|
||||
out := fields[:0]
|
||||
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||
// One iteration per name.
|
||||
// Find the sequence of fields with the name of this first field.
|
||||
fi := fields[i]
|
||||
name := fi.name
|
||||
for advance = 1; i+advance < len(fields); advance++ {
|
||||
fj := fields[i+advance]
|
||||
if fj.name != name {
|
||||
break
|
||||
}
|
||||
}
|
||||
if advance == 1 { // Only one field with this name
|
||||
out = append(out, fi)
|
||||
continue
|
||||
}
|
||||
dominant, ok := dominantField(fields[i : i+advance])
|
||||
if ok {
|
||||
out = append(out, dominant)
|
||||
}
|
||||
}
|
||||
|
||||
fields = out
|
||||
sort.Sort(byIndex(fields))
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// dominantField looks through the fields, all of which are known to
|
||||
// have the same name, to find the single field that dominates the
|
||||
// others using Go's embedding rules, modified by the presence of
|
||||
// TOML tags. If there are multiple top-level fields, the boolean
|
||||
// will be false: This condition is an error in Go and we skip all
|
||||
// the fields.
|
||||
func dominantField(fields []field) (field, bool) {
|
||||
// The fields are sorted in increasing index-length order. The winner
|
||||
// must therefore be one with the shortest index length. Drop all
|
||||
// longer entries, which is easy: just truncate the slice.
|
||||
length := len(fields[0].index)
|
||||
tagged := -1 // Index of first tagged field.
|
||||
for i, f := range fields {
|
||||
if len(f.index) > length {
|
||||
fields = fields[:i]
|
||||
break
|
||||
}
|
||||
if f.tag {
|
||||
if tagged >= 0 {
|
||||
// Multiple tagged fields at the same level: conflict.
|
||||
// Return no field.
|
||||
return field{}, false
|
||||
}
|
||||
tagged = i
|
||||
}
|
||||
}
|
||||
if tagged >= 0 {
|
||||
return fields[tagged], true
|
||||
}
|
||||
// All remaining fields have the same length. If there's more than one,
|
||||
// we have a conflict (two fields named "X" at the same level) and we
|
||||
// return no field.
|
||||
if len(fields) > 1 {
|
||||
return field{}, false
|
||||
}
|
||||
return fields[0], true
|
||||
}
|
||||
|
||||
var fieldCache struct {
|
||||
sync.RWMutex
|
||||
m map[reflect.Type][]field
|
||||
}
|
||||
|
||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||
func cachedTypeFields(t reflect.Type) []field {
|
||||
fieldCache.RLock()
|
||||
f := fieldCache.m[t]
|
||||
fieldCache.RUnlock()
|
||||
if f != nil {
|
||||
return f
|
||||
}
|
||||
|
||||
// Compute fields without lock.
|
||||
// Might duplicate effort but won't hold other computations back.
|
||||
f = typeFields(t)
|
||||
if f == nil {
|
||||
f = []field{}
|
||||
}
|
||||
|
||||
fieldCache.Lock()
|
||||
if fieldCache.m == nil {
|
||||
fieldCache.m = map[reflect.Type][]field{}
|
||||
}
|
||||
fieldCache.m[t] = f
|
||||
fieldCache.Unlock()
|
||||
return f
|
||||
}
|
||||
65
vendor/github.com/BurntSushi/toml/type_toml.go
generated
vendored
Normal file
65
vendor/github.com/BurntSushi/toml/type_toml.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package toml
|
||||
|
||||
// tomlType represents any Go type that corresponds to a TOML type.
|
||||
// While the first draft of the TOML spec has a simplistic type system that
|
||||
// probably doesn't need this level of sophistication, we seem to be militating
|
||||
// toward adding real composite types.
|
||||
type tomlType interface {
|
||||
typeString() string
|
||||
}
|
||||
|
||||
// typeEqual accepts any two types and returns true if they are equal.
|
||||
func typeEqual(t1, t2 tomlType) bool {
|
||||
if t1 == nil || t2 == nil {
|
||||
return false
|
||||
}
|
||||
return t1.typeString() == t2.typeString()
|
||||
}
|
||||
|
||||
func typeIsTable(t tomlType) bool {
|
||||
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
||||
}
|
||||
|
||||
type tomlBaseType string
|
||||
|
||||
func (btype tomlBaseType) typeString() string { return string(btype) }
|
||||
func (btype tomlBaseType) String() string { return btype.typeString() }
|
||||
|
||||
var (
|
||||
tomlInteger tomlBaseType = "Integer"
|
||||
tomlFloat tomlBaseType = "Float"
|
||||
tomlDatetime tomlBaseType = "Datetime"
|
||||
tomlString tomlBaseType = "String"
|
||||
tomlBool tomlBaseType = "Bool"
|
||||
tomlArray tomlBaseType = "Array"
|
||||
tomlHash tomlBaseType = "Hash"
|
||||
tomlArrayHash tomlBaseType = "ArrayHash"
|
||||
)
|
||||
|
||||
// typeOfPrimitive returns a tomlType of any primitive value in TOML.
|
||||
// Primitive values are: Integer, Float, Datetime, String and Bool.
|
||||
//
|
||||
// Passing a lexer item other than the following will cause a BUG message
|
||||
// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
|
||||
func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
||||
switch lexItem.typ {
|
||||
case itemInteger:
|
||||
return tomlInteger
|
||||
case itemFloat:
|
||||
return tomlFloat
|
||||
case itemDatetime:
|
||||
return tomlDatetime
|
||||
case itemString, itemStringEsc:
|
||||
return tomlString
|
||||
case itemMultilineString:
|
||||
return tomlString
|
||||
case itemRawString:
|
||||
return tomlString
|
||||
case itemRawMultilineString:
|
||||
return tomlString
|
||||
case itemBool:
|
||||
return tomlBool
|
||||
}
|
||||
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
|
||||
panic("unreachable")
|
||||
}
|
||||
27
vendor/golang.org/x/exp/typeparams/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/exp/typeparams/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
182
vendor/golang.org/x/exp/typeparams/common.go
generated
vendored
Normal file
182
vendor/golang.org/x/exp/typeparams/common.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package typeparams contains common utilities for writing tools that interact
|
||||
// with generic Go code, as introduced with Go 1.18.
|
||||
//
|
||||
// Many of the types and functions in this package are proxies for the new APIs
|
||||
// introduced in the standard library with Go 1.18. For example, the
|
||||
// typeparams.Union type is an alias for go/types.Union, and the ForTypeSpec
|
||||
// function returns the value of the go/ast.TypeSpec.TypeParams field. At Go
|
||||
// versions older than 1.18 these helpers are implemented as stubs, allowing
|
||||
// users of this package to write code that handles generic constructs inline,
|
||||
// even if the Go version being used to compile does not support generics.
|
||||
//
|
||||
// Additionally, this package contains common utilities for working with the
|
||||
// new generic constructs, to supplement the standard library APIs. Notably,
|
||||
// the NormalTerms API computes a minimal representation of the structural
|
||||
// restrictions on a type parameter. In the future, these supplemental APIs may
|
||||
// be available in the standard library..
|
||||
package typeparams
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// Enabled reports whether type parameters are enabled in the current build
|
||||
// environment.
|
||||
func Enabled() bool {
|
||||
return enabled
|
||||
}
|
||||
|
||||
// UnpackIndexExpr extracts data from AST nodes that represent index
|
||||
// expressions.
|
||||
//
|
||||
// For an ast.IndexExpr, the resulting indices slice will contain exactly one
|
||||
// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable
|
||||
// number of index expressions.
|
||||
//
|
||||
// For nodes that don't represent index expressions, the first return value of
|
||||
// UnpackIndexExpr will be nil.
|
||||
func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) {
|
||||
switch e := n.(type) {
|
||||
case *ast.IndexExpr:
|
||||
return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack
|
||||
case *IndexListExpr:
|
||||
return e.X, e.Lbrack, e.Indices, e.Rbrack
|
||||
}
|
||||
return nil, token.NoPos, nil, token.NoPos
|
||||
}
|
||||
|
||||
// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on
|
||||
// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0
|
||||
// will panic.
|
||||
func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr {
|
||||
switch len(indices) {
|
||||
case 0:
|
||||
panic("empty indices")
|
||||
case 1:
|
||||
return &ast.IndexExpr{
|
||||
X: x,
|
||||
Lbrack: lbrack,
|
||||
Index: indices[0],
|
||||
Rbrack: rbrack,
|
||||
}
|
||||
default:
|
||||
return &IndexListExpr{
|
||||
X: x,
|
||||
Lbrack: lbrack,
|
||||
Indices: indices,
|
||||
Rbrack: rbrack,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsTypeParam reports whether t is a type parameter.
|
||||
func IsTypeParam(t types.Type) bool {
|
||||
_, ok := t.(*TypeParam)
|
||||
return ok
|
||||
}
|
||||
|
||||
// OriginMethod returns the origin method associated with the method fn. For
|
||||
// methods on a non-generic receiver base type, this is just fn. However, for
|
||||
// methods with a generic receiver, OriginMethod returns the corresponding
|
||||
// method in the method set of the origin type.
|
||||
//
|
||||
// As a special case, if fn is not a method (has no receiver), OriginMethod
|
||||
// returns fn.
|
||||
func OriginMethod(fn *types.Func) *types.Func {
|
||||
recv := fn.Type().(*types.Signature).Recv()
|
||||
if recv == nil {
|
||||
return fn
|
||||
}
|
||||
base := recv.Type()
|
||||
p, isPtr := base.(*types.Pointer)
|
||||
if isPtr {
|
||||
base = p.Elem()
|
||||
}
|
||||
named, isNamed := base.(*types.Named)
|
||||
if !isNamed {
|
||||
// Receiver is a *types.Interface.
|
||||
return fn
|
||||
}
|
||||
if ForNamed(named).Len() == 0 {
|
||||
// Receiver base has no type parameters, so we can avoid the lookup below.
|
||||
return fn
|
||||
}
|
||||
orig := NamedTypeOrigin(named)
|
||||
gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name())
|
||||
return gfn.(*types.Func)
|
||||
}
|
||||
|
||||
// GenericAssignableTo is a generalization of types.AssignableTo that
|
||||
// implements the following rule for uninstantiated generic types:
|
||||
//
|
||||
// If V and T are generic named types, then V is considered assignable to T if,
|
||||
// for every possible instantation of V[A_1, ..., A_N], the instantiation
|
||||
// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N].
|
||||
//
|
||||
// If T has structural constraints, they must be satisfied by V.
|
||||
//
|
||||
// For example, consider the following type declarations:
|
||||
//
|
||||
// type Interface[T any] interface {
|
||||
// Accept(T)
|
||||
// }
|
||||
//
|
||||
// type Container[T any] struct {
|
||||
// Element T
|
||||
// }
|
||||
//
|
||||
// func (c Container[T]) Accept(t T) { c.Element = t }
|
||||
//
|
||||
// In this case, GenericAssignableTo reports that instantiations of Container
|
||||
// are assignable to the corresponding instantiation of Interface.
|
||||
func GenericAssignableTo(ctxt *Context, V, T types.Type) bool {
|
||||
// If V and T are not both named, or do not have matching non-empty type
|
||||
// parameter lists, fall back on types.AssignableTo.
|
||||
|
||||
VN, Vnamed := V.(*types.Named)
|
||||
TN, Tnamed := T.(*types.Named)
|
||||
if !Vnamed || !Tnamed {
|
||||
return types.AssignableTo(V, T)
|
||||
}
|
||||
|
||||
vtparams := ForNamed(VN)
|
||||
ttparams := ForNamed(TN)
|
||||
if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || NamedTypeArgs(VN).Len() != 0 || NamedTypeArgs(TN).Len() != 0 {
|
||||
return types.AssignableTo(V, T)
|
||||
}
|
||||
|
||||
// V and T have the same (non-zero) number of type params. Instantiate both
|
||||
// with the type parameters of V. This must always succeed for V, and will
|
||||
// succeed for T if and only if the type set of each type parameter of V is a
|
||||
// subset of the type set of the corresponding type parameter of T, meaning
|
||||
// that every instantiation of V corresponds to a valid instantiation of T.
|
||||
|
||||
// Minor optimization: ensure we share a context across the two
|
||||
// instantiations below.
|
||||
if ctxt == nil {
|
||||
ctxt = NewContext()
|
||||
}
|
||||
|
||||
var targs []types.Type
|
||||
for i := 0; i < vtparams.Len(); i++ {
|
||||
targs = append(targs, vtparams.At(i))
|
||||
}
|
||||
|
||||
vinst, err := Instantiate(ctxt, V, targs, true)
|
||||
if err != nil {
|
||||
panic("type parameters should satisfy their own constraints")
|
||||
}
|
||||
|
||||
tinst, err := Instantiate(ctxt, T, targs, true)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return types.AssignableTo(vinst, tinst)
|
||||
}
|
||||
200
vendor/golang.org/x/exp/typeparams/normalize.go
generated
vendored
Normal file
200
vendor/golang.org/x/exp/typeparams/normalize.go
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package typeparams
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
// ErrEmptyTypeSet is returned if a type set computation results in a type set
|
||||
// with no types.
|
||||
var ErrEmptyTypeSet = errors.New("empty type set")
|
||||
|
||||
// NormalTerms returns a slice of terms representing the normalized structural
|
||||
// type restrictions of a type, if any.
|
||||
//
|
||||
// For all types whose underlying type is not *types.TypeParam,
|
||||
// *types.Interface, or *types.Union, this is just a single term with Tilde()
|
||||
// == false and Type() == typ. For types whose underlying type is
|
||||
// *types.TypeParam, *types.Interface, and *types.Union, see below.
|
||||
//
|
||||
// Structural type restrictions of a type parameter are created via
|
||||
// non-interface types embedded in its constraint interface (directly, or via a
|
||||
// chain of interface embeddings). For example, in the declaration type T[P
|
||||
// interface{~int; m()}] int is the structural restriction of the type
|
||||
// parameter P is ~int.
|
||||
//
|
||||
// With interface embedding and unions, the specification of structural type
|
||||
// restrictions may be arbitrarily complex. For example, consider the
|
||||
// following:
|
||||
//
|
||||
// type A interface{ ~string|~[]byte }
|
||||
//
|
||||
// type B interface{ int|string }
|
||||
//
|
||||
// type C interface { ~string|~int }
|
||||
//
|
||||
// type T[P interface{ A|B; C }] int
|
||||
//
|
||||
// In this example, the structural type restriction of P is ~string|int: A|B
|
||||
// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int,
|
||||
// which when intersected with C (~string|~int) yields ~string|int.
|
||||
//
|
||||
// NormalTerms computes these expansions and reductions, producing a
|
||||
// "normalized" form of the embeddings. A structural restriction is normalized
|
||||
// if it is a single union containing no interface terms, and is minimal in the
|
||||
// sense that removing any term changes the set of types satisfying the
|
||||
// constraint. It is left as a proof for the reader that, modulo sorting, there
|
||||
// is exactly one such normalized form.
|
||||
//
|
||||
// Because the minimal representation always takes this form, NormalTerms
|
||||
// returns a slice of tilde terms corresponding to the terms of the union in
|
||||
// the normalized structural restriction. An error is returned if the type is
|
||||
// invalid, exceeds complexity bounds, or has an empty type set. In the latter
|
||||
// case, NormalTerms returns ErrEmptyTypeSet.
|
||||
//
|
||||
// NormalTerms makes no guarantees about the order of terms, except that it
|
||||
// is deterministic.
|
||||
func NormalTerms(typ types.Type) ([]*Term, error) {
|
||||
if tparam, ok := typ.(*TypeParam); ok {
|
||||
constraint := tparam.Constraint()
|
||||
if constraint == nil {
|
||||
return nil, fmt.Errorf("%s has nil constraint", tparam)
|
||||
}
|
||||
iface, _ := constraint.Underlying().(*types.Interface)
|
||||
if iface == nil {
|
||||
return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying())
|
||||
}
|
||||
typ = iface
|
||||
}
|
||||
tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tset.terms.isEmpty() {
|
||||
return nil, ErrEmptyTypeSet
|
||||
}
|
||||
if tset.terms.isAll() {
|
||||
return nil, nil
|
||||
}
|
||||
var terms []*Term
|
||||
for _, term := range tset.terms {
|
||||
terms = append(terms, NewTerm(term.tilde, term.typ))
|
||||
}
|
||||
return terms, nil
|
||||
}
|
||||
|
||||
// A termSet holds the normalized set of terms for a given type.
|
||||
//
|
||||
// The name termSet is intentionally distinct from 'type set': a type set is
|
||||
// all types that implement a type (and includes method restrictions), whereas
|
||||
// a term set just represents the structural restrictions on a type.
|
||||
type termSet struct {
|
||||
complete bool
|
||||
terms termlist
|
||||
}
|
||||
|
||||
func indentf(depth int, format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...)
|
||||
}
|
||||
|
||||
func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) {
|
||||
if t == nil {
|
||||
panic("nil type")
|
||||
}
|
||||
|
||||
if debug {
|
||||
indentf(depth, "%s", t.String())
|
||||
defer func() {
|
||||
if err != nil {
|
||||
indentf(depth, "=> %s", err)
|
||||
} else {
|
||||
indentf(depth, "=> %s", res.terms.String())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
const maxTermCount = 100
|
||||
if tset, ok := seen[t]; ok {
|
||||
if !tset.complete {
|
||||
return nil, fmt.Errorf("cycle detected in the declaration of %s", t)
|
||||
}
|
||||
return tset, nil
|
||||
}
|
||||
|
||||
// Mark the current type as seen to avoid infinite recursion.
|
||||
tset := new(termSet)
|
||||
defer func() {
|
||||
tset.complete = true
|
||||
}()
|
||||
seen[t] = tset
|
||||
|
||||
switch u := t.Underlying().(type) {
|
||||
case *types.Interface:
|
||||
// The term set of an interface is the intersection of the term sets of its
|
||||
// embedded types.
|
||||
tset.terms = allTermlist
|
||||
for i := 0; i < u.NumEmbeddeds(); i++ {
|
||||
embedded := u.EmbeddedType(i)
|
||||
if _, ok := embedded.Underlying().(*TypeParam); ok {
|
||||
return nil, fmt.Errorf("invalid embedded type %T", embedded)
|
||||
}
|
||||
tset2, err := computeTermSetInternal(embedded, seen, depth+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tset.terms = tset.terms.intersect(tset2.terms)
|
||||
}
|
||||
case *Union:
|
||||
// The term set of a union is the union of term sets of its terms.
|
||||
tset.terms = nil
|
||||
for i := 0; i < u.Len(); i++ {
|
||||
t := u.Term(i)
|
||||
var terms termlist
|
||||
switch t.Type().Underlying().(type) {
|
||||
case *types.Interface:
|
||||
tset2, err := computeTermSetInternal(t.Type(), seen, depth+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
terms = tset2.terms
|
||||
case *TypeParam, *Union:
|
||||
// A stand-alone type parameter or union is not permitted as union
|
||||
// term.
|
||||
return nil, fmt.Errorf("invalid union term %T", t)
|
||||
default:
|
||||
if t.Type() == types.Typ[types.Invalid] {
|
||||
continue
|
||||
}
|
||||
terms = termlist{{t.Tilde(), t.Type()}}
|
||||
}
|
||||
tset.terms = tset.terms.union(terms)
|
||||
if len(tset.terms) > maxTermCount {
|
||||
return nil, fmt.Errorf("exceeded max term count %d", maxTermCount)
|
||||
}
|
||||
}
|
||||
case *TypeParam:
|
||||
panic("unreachable")
|
||||
default:
|
||||
// For all other types, the term set is just a single non-tilde term
|
||||
// holding the type itself.
|
||||
if u != types.Typ[types.Invalid] {
|
||||
tset.terms = termlist{{false, t}}
|
||||
}
|
||||
}
|
||||
return tset, nil
|
||||
}
|
||||
|
||||
// under is a facade for the go/types internal function of the same name. It is
|
||||
// used by typeterm.go.
|
||||
func under(t types.Type) types.Type {
|
||||
return t.Underlying()
|
||||
}
|
||||
172
vendor/golang.org/x/exp/typeparams/termlist.go
generated
vendored
Normal file
172
vendor/golang.org/x/exp/typeparams/termlist.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Code generated by copytermlist.go DO NOT EDIT.
|
||||
|
||||
package typeparams
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// A termlist represents the type set represented by the union
|
||||
// t1 ∪ y2 ∪ ... tn of the type sets of the terms t1 to tn.
|
||||
// A termlist is in normal form if all terms are disjoint.
|
||||
// termlist operations don't require the operands to be in
|
||||
// normal form.
|
||||
type termlist []*term
|
||||
|
||||
// allTermlist represents the set of all types.
|
||||
// It is in normal form.
|
||||
var allTermlist = termlist{new(term)}
|
||||
|
||||
// String prints the termlist exactly (without normalization).
|
||||
func (xl termlist) String() string {
|
||||
if len(xl) == 0 {
|
||||
return "∅"
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for i, x := range xl {
|
||||
if i > 0 {
|
||||
buf.WriteString(" ∪ ")
|
||||
}
|
||||
buf.WriteString(x.String())
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// isEmpty reports whether the termlist xl represents the empty set of types.
|
||||
func (xl termlist) isEmpty() bool {
|
||||
// If there's a non-nil term, the entire list is not empty.
|
||||
// If the termlist is in normal form, this requires at most
|
||||
// one iteration.
|
||||
for _, x := range xl {
|
||||
if x != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isAll reports whether the termlist xl represents the set of all types.
|
||||
func (xl termlist) isAll() bool {
|
||||
// If there's a 𝓤 term, the entire list is 𝓤.
|
||||
// If the termlist is in normal form, this requires at most
|
||||
// one iteration.
|
||||
for _, x := range xl {
|
||||
if x != nil && x.typ == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// norm returns the normal form of xl.
|
||||
func (xl termlist) norm() termlist {
|
||||
// Quadratic algorithm, but good enough for now.
|
||||
// TODO(gri) fix asymptotic performance
|
||||
used := make([]bool, len(xl))
|
||||
var rl termlist
|
||||
for i, xi := range xl {
|
||||
if xi == nil || used[i] {
|
||||
continue
|
||||
}
|
||||
for j := i + 1; j < len(xl); j++ {
|
||||
xj := xl[j]
|
||||
if xj == nil || used[j] {
|
||||
continue
|
||||
}
|
||||
if u1, u2 := xi.union(xj); u2 == nil {
|
||||
// If we encounter a 𝓤 term, the entire list is 𝓤.
|
||||
// Exit early.
|
||||
// (Note that this is not just an optimization;
|
||||
// if we continue, we may end up with a 𝓤 term
|
||||
// and other terms and the result would not be
|
||||
// in normal form.)
|
||||
if u1.typ == nil {
|
||||
return allTermlist
|
||||
}
|
||||
xi = u1
|
||||
used[j] = true // xj is now unioned into xi - ignore it in future iterations
|
||||
}
|
||||
}
|
||||
rl = append(rl, xi)
|
||||
}
|
||||
return rl
|
||||
}
|
||||
|
||||
// If the type set represented by xl is specified by a single (non-𝓤) term,
|
||||
// singleType returns that type. Otherwise it returns nil.
|
||||
func (xl termlist) singleType() types.Type {
|
||||
if nl := xl.norm(); len(nl) == 1 {
|
||||
return nl[0].typ // if nl.isAll() then typ is nil, which is ok
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// union returns the union xl ∪ yl.
|
||||
func (xl termlist) union(yl termlist) termlist {
|
||||
return append(xl, yl...).norm()
|
||||
}
|
||||
|
||||
// intersect returns the intersection xl ∩ yl.
|
||||
func (xl termlist) intersect(yl termlist) termlist {
|
||||
if xl.isEmpty() || yl.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Quadratic algorithm, but good enough for now.
|
||||
// TODO(gri) fix asymptotic performance
|
||||
var rl termlist
|
||||
for _, x := range xl {
|
||||
for _, y := range yl {
|
||||
if r := x.intersect(y); r != nil {
|
||||
rl = append(rl, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return rl.norm()
|
||||
}
|
||||
|
||||
// equal reports whether xl and yl represent the same type set.
|
||||
func (xl termlist) equal(yl termlist) bool {
|
||||
// TODO(gri) this should be more efficient
|
||||
return xl.subsetOf(yl) && yl.subsetOf(xl)
|
||||
}
|
||||
|
||||
// includes reports whether t ∈ xl.
|
||||
func (xl termlist) includes(t types.Type) bool {
|
||||
for _, x := range xl {
|
||||
if x.includes(t) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// supersetOf reports whether y ⊆ xl.
|
||||
func (xl termlist) supersetOf(y *term) bool {
|
||||
for _, x := range xl {
|
||||
if y.subsetOf(x) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// subsetOf reports whether xl ⊆ yl.
|
||||
func (xl termlist) subsetOf(yl termlist) bool {
|
||||
if yl.isEmpty() {
|
||||
return xl.isEmpty()
|
||||
}
|
||||
|
||||
// each term x of xl must be a subset of yl
|
||||
for _, x := range xl {
|
||||
if !yl.supersetOf(x) {
|
||||
return false // x is not a subset yl
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
201
vendor/golang.org/x/exp/typeparams/typeparams_go117.go
generated
vendored
Normal file
201
vendor/golang.org/x/exp/typeparams/typeparams_go117.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.18
|
||||
|
||||
package typeparams
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
const enabled = false
|
||||
|
||||
func unsupported() {
|
||||
panic("type parameters are unsupported at this go version")
|
||||
}
|
||||
|
||||
// IndexListExpr is a placeholder type, as type parameters are not supported at
|
||||
// this Go version. Its methods panic on use.
|
||||
type IndexListExpr struct {
|
||||
ast.Expr
|
||||
X ast.Expr // expression
|
||||
Lbrack token.Pos // position of "["
|
||||
Indices []ast.Expr // index expressions
|
||||
Rbrack token.Pos // position of "]"
|
||||
}
|
||||
|
||||
func (*IndexListExpr) Pos() token.Pos { unsupported(); return token.NoPos }
|
||||
func (*IndexListExpr) End() token.Pos { unsupported(); return token.NoPos }
|
||||
|
||||
// ForTypeSpec returns an empty field list, as type parameters on not supported
|
||||
// at this Go version.
|
||||
func ForTypeSpec(*ast.TypeSpec) *ast.FieldList {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForFuncType returns an empty field list, as type parameters are not
|
||||
// supported at this Go version.
|
||||
func ForFuncType(*ast.FuncType) *ast.FieldList {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TypeParam is a placeholder type, as type parameters are not supported at
|
||||
// this Go version. Its methods panic on use.
|
||||
type TypeParam struct{ types.Type }
|
||||
|
||||
func (*TypeParam) String() string { unsupported(); return "" }
|
||||
func (*TypeParam) Underlying() types.Type { unsupported(); return nil }
|
||||
func (*TypeParam) Index() int { unsupported(); return 0 }
|
||||
func (*TypeParam) Constraint() types.Type { unsupported(); return nil }
|
||||
func (*TypeParam) SetConstraint(types.Type) { unsupported() }
|
||||
func (*TypeParam) Obj() *types.TypeName { unsupported(); return nil }
|
||||
|
||||
// TypeParamList is a placeholder for an empty type parameter list.
|
||||
type TypeParamList struct{}
|
||||
|
||||
func (*TypeParamList) Len() int { return 0 }
|
||||
func (*TypeParamList) At(int) *TypeParam { unsupported(); return nil }
|
||||
|
||||
// TypeList is a placeholder for an empty type list.
|
||||
type TypeList struct{}
|
||||
|
||||
func (*TypeList) Len() int { return 0 }
|
||||
func (*TypeList) At(int) types.Type { unsupported(); return nil }
|
||||
|
||||
// NewTypeParam is unsupported at this Go version, and panics.
|
||||
func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam {
|
||||
unsupported()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSignatureType calls types.NewSignature, panicking if recvTypeParams or
|
||||
// typeParams is non-empty.
|
||||
func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature {
|
||||
if len(recvTypeParams) != 0 || len(typeParams) != 0 {
|
||||
unsupported()
|
||||
}
|
||||
return types.NewSignature(recv, params, results, variadic)
|
||||
}
|
||||
|
||||
// ForSignature returns an empty slice.
|
||||
func ForSignature(*types.Signature) *TypeParamList {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecvTypeParams returns a nil slice.
|
||||
func RecvTypeParams(sig *types.Signature) *TypeParamList {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsComparable returns false, as no interfaces are type-restricted at this Go
|
||||
// version.
|
||||
func IsComparable(*types.Interface) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsMethodSet returns true, as no interfaces are type-restricted at this Go
|
||||
// version.
|
||||
func IsMethodSet(*types.Interface) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsImplicit returns false, as no interfaces are implicit at this Go version.
|
||||
func IsImplicit(*types.Interface) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MarkImplicit does nothing, because this Go version does not have implicit
|
||||
// interfaces.
|
||||
func MarkImplicit(*types.Interface) {}
|
||||
|
||||
// ForNamed returns an empty type parameter list, as type parameters are not
|
||||
// supported at this Go version.
|
||||
func ForNamed(*types.Named) *TypeParamList {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetForNamed panics if tparams is non-empty.
|
||||
func SetForNamed(_ *types.Named, tparams []*TypeParam) {
|
||||
if len(tparams) > 0 {
|
||||
unsupported()
|
||||
}
|
||||
}
|
||||
|
||||
// NamedTypeArgs returns nil.
|
||||
func NamedTypeArgs(*types.Named) *TypeList {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NamedTypeOrigin is the identity method at this Go version.
|
||||
func NamedTypeOrigin(named *types.Named) types.Type {
|
||||
return named
|
||||
}
|
||||
|
||||
// Term holds information about a structural type restriction.
|
||||
type Term struct {
|
||||
tilde bool
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (m *Term) Tilde() bool { return m.tilde }
|
||||
func (m *Term) Type() types.Type { return m.typ }
|
||||
func (m *Term) String() string {
|
||||
pre := ""
|
||||
if m.tilde {
|
||||
pre = "~"
|
||||
}
|
||||
return pre + m.typ.String()
|
||||
}
|
||||
|
||||
// NewTerm creates a new placeholder term type.
|
||||
func NewTerm(tilde bool, typ types.Type) *Term {
|
||||
return &Term{tilde, typ}
|
||||
}
|
||||
|
||||
// Union is a placeholder type, as type parameters are not supported at this Go
|
||||
// version. Its methods panic on use.
|
||||
type Union struct{ types.Type }
|
||||
|
||||
func (*Union) String() string { unsupported(); return "" }
|
||||
func (*Union) Underlying() types.Type { unsupported(); return nil }
|
||||
func (*Union) Len() int { return 0 }
|
||||
func (*Union) Term(i int) *Term { unsupported(); return nil }
|
||||
|
||||
// NewUnion is unsupported at this Go version, and panics.
|
||||
func NewUnion(terms []*Term) *Union {
|
||||
unsupported()
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitInstances is a noop at this Go version.
|
||||
func InitInstances(*types.Info) {}
|
||||
|
||||
// Instance is a placeholder type, as type parameters are not supported at this
|
||||
// Go version.
|
||||
type Instance struct {
|
||||
TypeArgs *TypeList
|
||||
Type types.Type
|
||||
}
|
||||
|
||||
// GetInstances returns a nil map, as type parameters are not supported at this
|
||||
// Go version.
|
||||
func GetInstances(info *types.Info) map[*ast.Ident]Instance { return nil }
|
||||
|
||||
// Context is a placeholder type, as type parameters are not supported at
|
||||
// this Go version.
|
||||
type Context struct{}
|
||||
|
||||
// NewContext returns a placeholder Context instance.
|
||||
func NewContext() *Context {
|
||||
return &Context{}
|
||||
}
|
||||
|
||||
// Instantiate is unsupported on this Go version, and panics.
|
||||
func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) {
|
||||
unsupported()
|
||||
return nil, nil
|
||||
}
|
||||
147
vendor/golang.org/x/exp/typeparams/typeparams_go118.go
generated
vendored
Normal file
147
vendor/golang.org/x/exp/typeparams/typeparams_go118.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
|
||||
package typeparams
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
const enabled = true
|
||||
|
||||
// IndexListExpr is an alias for ast.IndexListExpr.
|
||||
type IndexListExpr = ast.IndexListExpr
|
||||
|
||||
// ForTypeSpec returns n.TypeParams.
|
||||
func ForTypeSpec(n *ast.TypeSpec) *ast.FieldList {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
return n.TypeParams
|
||||
}
|
||||
|
||||
// ForFuncType returns n.TypeParams.
|
||||
func ForFuncType(n *ast.FuncType) *ast.FieldList {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
return n.TypeParams
|
||||
}
|
||||
|
||||
// TypeParam is an alias for types.TypeParam
|
||||
type TypeParam = types.TypeParam
|
||||
|
||||
// TypeParamList is an alias for types.TypeParamList
|
||||
type TypeParamList = types.TypeParamList
|
||||
|
||||
// TypeList is an alias for types.TypeList
|
||||
type TypeList = types.TypeList
|
||||
|
||||
// NewTypeParam calls types.NewTypeParam.
|
||||
func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam {
|
||||
return types.NewTypeParam(name, constraint)
|
||||
}
|
||||
|
||||
// NewSignatureType calls types.NewSignatureType.
|
||||
func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature {
|
||||
return types.NewSignatureType(recv, recvTypeParams, typeParams, params, results, variadic)
|
||||
}
|
||||
|
||||
// ForSignature returns sig.TypeParams()
|
||||
func ForSignature(sig *types.Signature) *TypeParamList {
|
||||
return sig.TypeParams()
|
||||
}
|
||||
|
||||
// RecvTypeParams returns sig.RecvTypeParams().
|
||||
func RecvTypeParams(sig *types.Signature) *TypeParamList {
|
||||
return sig.RecvTypeParams()
|
||||
}
|
||||
|
||||
// IsComparable calls iface.IsComparable().
|
||||
func IsComparable(iface *types.Interface) bool {
|
||||
return iface.IsComparable()
|
||||
}
|
||||
|
||||
// IsMethodSet calls iface.IsMethodSet().
|
||||
func IsMethodSet(iface *types.Interface) bool {
|
||||
return iface.IsMethodSet()
|
||||
}
|
||||
|
||||
// IsImplicit calls iface.IsImplicit().
|
||||
func IsImplicit(iface *types.Interface) bool {
|
||||
return iface.IsImplicit()
|
||||
}
|
||||
|
||||
// MarkImplicit calls iface.MarkImplicit().
|
||||
func MarkImplicit(iface *types.Interface) {
|
||||
iface.MarkImplicit()
|
||||
}
|
||||
|
||||
// ForNamed extracts the (possibly empty) type parameter object list from
|
||||
// named.
|
||||
func ForNamed(named *types.Named) *TypeParamList {
|
||||
return named.TypeParams()
|
||||
}
|
||||
|
||||
// SetForNamed sets the type params tparams on n. Each tparam must be of
|
||||
// dynamic type *types.TypeParam.
|
||||
func SetForNamed(n *types.Named, tparams []*TypeParam) {
|
||||
n.SetTypeParams(tparams)
|
||||
}
|
||||
|
||||
// NamedTypeArgs returns named.TypeArgs().
|
||||
func NamedTypeArgs(named *types.Named) *TypeList {
|
||||
return named.TypeArgs()
|
||||
}
|
||||
|
||||
// NamedTypeOrigin returns named.Orig().
|
||||
func NamedTypeOrigin(named *types.Named) types.Type {
|
||||
return named.Origin()
|
||||
}
|
||||
|
||||
// Term is an alias for types.Term.
|
||||
type Term = types.Term
|
||||
|
||||
// NewTerm calls types.NewTerm.
|
||||
func NewTerm(tilde bool, typ types.Type) *Term {
|
||||
return types.NewTerm(tilde, typ)
|
||||
}
|
||||
|
||||
// Union is an alias for types.Union
|
||||
type Union = types.Union
|
||||
|
||||
// NewUnion calls types.NewUnion.
|
||||
func NewUnion(terms []*Term) *Union {
|
||||
return types.NewUnion(terms)
|
||||
}
|
||||
|
||||
// InitInstances initializes info to record information about type and function
|
||||
// instances.
|
||||
func InitInstances(info *types.Info) {
|
||||
info.Instances = make(map[*ast.Ident]types.Instance)
|
||||
}
|
||||
|
||||
// Instance is an alias for types.Instance.
|
||||
type Instance = types.Instance
|
||||
|
||||
// GetInstances returns info.Instances.
|
||||
func GetInstances(info *types.Info) map[*ast.Ident]Instance {
|
||||
return info.Instances
|
||||
}
|
||||
|
||||
// Context is an alias for types.Context.
|
||||
type Context = types.Context
|
||||
|
||||
// NewContext calls types.NewContext.
|
||||
func NewContext() *Context {
|
||||
return types.NewContext()
|
||||
}
|
||||
|
||||
// Instantiate calls types.Instantiate.
|
||||
func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) {
|
||||
return types.Instantiate(ctxt, typ, targs, validate)
|
||||
}
|
||||
169
vendor/golang.org/x/exp/typeparams/typeterm.go
generated
vendored
Normal file
169
vendor/golang.org/x/exp/typeparams/typeterm.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Code generated by copytermlist.go DO NOT EDIT.
|
||||
|
||||
package typeparams
|
||||
|
||||
import "go/types"
|
||||
|
||||
// A term describes elementary type sets:
|
||||
//
|
||||
// ∅: (*term)(nil) == ∅ // set of no types (empty set)
|
||||
// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse)
|
||||
// T: &term{false, T} == {T} // set of type T
|
||||
// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t
|
||||
type term struct {
|
||||
tilde bool // valid if typ != nil
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (x *term) String() string {
|
||||
switch {
|
||||
case x == nil:
|
||||
return "∅"
|
||||
case x.typ == nil:
|
||||
return "𝓤"
|
||||
case x.tilde:
|
||||
return "~" + x.typ.String()
|
||||
default:
|
||||
return x.typ.String()
|
||||
}
|
||||
}
|
||||
|
||||
// equal reports whether x and y represent the same type set.
|
||||
func (x *term) equal(y *term) bool {
|
||||
// easy cases
|
||||
switch {
|
||||
case x == nil || y == nil:
|
||||
return x == y
|
||||
case x.typ == nil || y.typ == nil:
|
||||
return x.typ == y.typ
|
||||
}
|
||||
// ∅ ⊂ x, y ⊂ 𝓤
|
||||
|
||||
return x.tilde == y.tilde && types.Identical(x.typ, y.typ)
|
||||
}
|
||||
|
||||
// union returns the union x ∪ y: zero, one, or two non-nil terms.
|
||||
func (x *term) union(y *term) (_, _ *term) {
|
||||
// easy cases
|
||||
switch {
|
||||
case x == nil && y == nil:
|
||||
return nil, nil // ∅ ∪ ∅ == ∅
|
||||
case x == nil:
|
||||
return y, nil // ∅ ∪ y == y
|
||||
case y == nil:
|
||||
return x, nil // x ∪ ∅ == x
|
||||
case x.typ == nil:
|
||||
return x, nil // 𝓤 ∪ y == 𝓤
|
||||
case y.typ == nil:
|
||||
return y, nil // x ∪ 𝓤 == 𝓤
|
||||
}
|
||||
// ∅ ⊂ x, y ⊂ 𝓤
|
||||
|
||||
if x.disjoint(y) {
|
||||
return x, y // x ∪ y == (x, y) if x ∩ y == ∅
|
||||
}
|
||||
// x.typ == y.typ
|
||||
|
||||
// ~t ∪ ~t == ~t
|
||||
// ~t ∪ T == ~t
|
||||
// T ∪ ~t == ~t
|
||||
// T ∪ T == T
|
||||
if x.tilde || !y.tilde {
|
||||
return x, nil
|
||||
}
|
||||
return y, nil
|
||||
}
|
||||
|
||||
// intersect returns the intersection x ∩ y.
|
||||
func (x *term) intersect(y *term) *term {
|
||||
// easy cases
|
||||
switch {
|
||||
case x == nil || y == nil:
|
||||
return nil // ∅ ∩ y == ∅ and ∩ ∅ == ∅
|
||||
case x.typ == nil:
|
||||
return y // 𝓤 ∩ y == y
|
||||
case y.typ == nil:
|
||||
return x // x ∩ 𝓤 == x
|
||||
}
|
||||
// ∅ ⊂ x, y ⊂ 𝓤
|
||||
|
||||
if x.disjoint(y) {
|
||||
return nil // x ∩ y == ∅ if x ∩ y == ∅
|
||||
}
|
||||
// x.typ == y.typ
|
||||
|
||||
// ~t ∩ ~t == ~t
|
||||
// ~t ∩ T == T
|
||||
// T ∩ ~t == T
|
||||
// T ∩ T == T
|
||||
if !x.tilde || y.tilde {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
// includes reports whether t ∈ x.
|
||||
func (x *term) includes(t types.Type) bool {
|
||||
// easy cases
|
||||
switch {
|
||||
case x == nil:
|
||||
return false // t ∈ ∅ == false
|
||||
case x.typ == nil:
|
||||
return true // t ∈ 𝓤 == true
|
||||
}
|
||||
// ∅ ⊂ x ⊂ 𝓤
|
||||
|
||||
u := t
|
||||
if x.tilde {
|
||||
u = under(u)
|
||||
}
|
||||
return types.Identical(x.typ, u)
|
||||
}
|
||||
|
||||
// subsetOf reports whether x ⊆ y.
|
||||
func (x *term) subsetOf(y *term) bool {
|
||||
// easy cases
|
||||
switch {
|
||||
case x == nil:
|
||||
return true // ∅ ⊆ y == true
|
||||
case y == nil:
|
||||
return false // x ⊆ ∅ == false since x != ∅
|
||||
case y.typ == nil:
|
||||
return true // x ⊆ 𝓤 == true
|
||||
case x.typ == nil:
|
||||
return false // 𝓤 ⊆ y == false since y != 𝓤
|
||||
}
|
||||
// ∅ ⊂ x, y ⊂ 𝓤
|
||||
|
||||
if x.disjoint(y) {
|
||||
return false // x ⊆ y == false if x ∩ y == ∅
|
||||
}
|
||||
// x.typ == y.typ
|
||||
|
||||
// ~t ⊆ ~t == true
|
||||
// ~t ⊆ T == false
|
||||
// T ⊆ ~t == true
|
||||
// T ⊆ T == true
|
||||
return !x.tilde || y.tilde
|
||||
}
|
||||
|
||||
// disjoint reports whether x ∩ y == ∅.
|
||||
// x.typ and y.typ must not be nil.
|
||||
func (x *term) disjoint(y *term) bool {
|
||||
if debug && (x.typ == nil || y.typ == nil) {
|
||||
panic("invalid argument(s)")
|
||||
}
|
||||
ux := x.typ
|
||||
if y.tilde {
|
||||
ux = under(ux)
|
||||
}
|
||||
uy := y.typ
|
||||
if x.tilde {
|
||||
uy = under(uy)
|
||||
}
|
||||
return !types.Identical(ux, uy)
|
||||
}
|
||||
269
vendor/golang.org/x/tools/go/analysis/analysis.go
generated
vendored
Normal file
269
vendor/golang.org/x/tools/go/analysis/analysis.go
generated
vendored
Normal file
@@ -0,0 +1,269 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package analysis
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An Analyzer describes an analysis function and its options.
|
||||
type Analyzer struct {
|
||||
// The Name of the analyzer must be a valid Go identifier
|
||||
// as it may appear in command-line flags, URLs, and so on.
|
||||
Name string
|
||||
|
||||
// Doc is the documentation for the analyzer.
|
||||
// The part before the first "\n\n" is the title
|
||||
// (no capital or period, max ~60 letters).
|
||||
Doc string
|
||||
|
||||
// URL holds an optional link to a web page with additional
|
||||
// documentation for this analyzer.
|
||||
URL string
|
||||
|
||||
// Flags defines any flags accepted by the analyzer.
|
||||
// The manner in which these flags are exposed to the user
|
||||
// depends on the driver which runs the analyzer.
|
||||
Flags flag.FlagSet
|
||||
|
||||
// Run applies the analyzer to a package.
|
||||
// It returns an error if the analyzer failed.
|
||||
//
|
||||
// On success, the Run function may return a result
|
||||
// computed by the Analyzer; its type must match ResultType.
|
||||
// The driver makes this result available as an input to
|
||||
// another Analyzer that depends directly on this one (see
|
||||
// Requires) when it analyzes the same package.
|
||||
//
|
||||
// To pass analysis results between packages (and thus
|
||||
// potentially between address spaces), use Facts, which are
|
||||
// serializable.
|
||||
Run func(*Pass) (any, error)
|
||||
|
||||
// RunDespiteErrors allows the driver to invoke
|
||||
// the Run method of this analyzer even on a
|
||||
// package that contains parse or type errors.
|
||||
// The [Pass.TypeErrors] field may consequently be non-empty.
|
||||
RunDespiteErrors bool
|
||||
|
||||
// Requires is a set of analyzers that must run successfully
|
||||
// before this one on a given package. This analyzer may inspect
|
||||
// the outputs produced by each analyzer in Requires.
|
||||
// The graph over analyzers implied by Requires edges must be acyclic.
|
||||
//
|
||||
// Requires establishes a "horizontal" dependency between
|
||||
// analysis passes (different analyzers, same package).
|
||||
Requires []*Analyzer
|
||||
|
||||
// ResultType is the type of the optional result of the Run function.
|
||||
ResultType reflect.Type
|
||||
|
||||
// FactTypes indicates that this analyzer imports and exports
|
||||
// Facts of the specified concrete types.
|
||||
// An analyzer that uses facts may assume that its import
|
||||
// dependencies have been similarly analyzed before it runs.
|
||||
// Facts must be pointers.
|
||||
//
|
||||
// FactTypes establishes a "vertical" dependency between
|
||||
// analysis passes (same analyzer, different packages).
|
||||
FactTypes []Fact
|
||||
}
|
||||
|
||||
func (a *Analyzer) String() string { return a.Name }
|
||||
|
||||
// A Pass provides information to the Run function that
|
||||
// applies a specific analyzer to a single Go package.
|
||||
//
|
||||
// It forms the interface between the analysis logic and the driver
|
||||
// program, and has both input and an output components.
|
||||
//
|
||||
// As in a compiler, one pass may depend on the result computed by another.
|
||||
//
|
||||
// The Run function should not call any of the Pass functions concurrently.
|
||||
type Pass struct {
|
||||
Analyzer *Analyzer // the identity of the current analyzer
|
||||
|
||||
// syntax and type information
|
||||
Fset *token.FileSet // file position information; Run may add new files
|
||||
Files []*ast.File // the abstract syntax tree of each file
|
||||
OtherFiles []string // names of non-Go files of this package
|
||||
IgnoredFiles []string // names of ignored source files in this package
|
||||
Pkg *types.Package // type information about the package
|
||||
TypesInfo *types.Info // type information about the syntax trees
|
||||
TypesSizes types.Sizes // function for computing sizes of types
|
||||
TypeErrors []types.Error // type errors (only if Analyzer.RunDespiteErrors)
|
||||
|
||||
Module *Module // the package's enclosing module (possibly nil in some drivers)
|
||||
|
||||
// Report reports a Diagnostic, a finding about a specific location
|
||||
// in the analyzed source code such as a potential mistake.
|
||||
// It may be called by the Run function.
|
||||
Report func(Diagnostic)
|
||||
|
||||
// ResultOf provides the inputs to this analysis pass, which are
|
||||
// the corresponding results of its prerequisite analyzers.
|
||||
// The map keys are the elements of Analysis.Required,
|
||||
// and the type of each corresponding value is the required
|
||||
// analysis's ResultType.
|
||||
ResultOf map[*Analyzer]any
|
||||
|
||||
// ReadFile returns the contents of the named file.
|
||||
//
|
||||
// The only valid file names are the elements of OtherFiles
|
||||
// and IgnoredFiles, and names returned by
|
||||
// Fset.File(f.FileStart).Name() for each f in Files.
|
||||
//
|
||||
// Analyzers must use this function (if provided) instead of
|
||||
// accessing the file system directly. This allows a driver to
|
||||
// provide a virtualized file tree (including, for example,
|
||||
// unsaved editor buffers) and to track dependencies precisely
|
||||
// to avoid unnecessary recomputation.
|
||||
ReadFile func(filename string) ([]byte, error)
|
||||
|
||||
// -- facts --
|
||||
|
||||
// ImportObjectFact retrieves a fact associated with obj.
|
||||
// Given a value ptr of type *T, where *T satisfies Fact,
|
||||
// ImportObjectFact copies the value to *ptr.
|
||||
//
|
||||
// ImportObjectFact panics if called after the pass is complete.
|
||||
// ImportObjectFact is not concurrency-safe.
|
||||
ImportObjectFact func(obj types.Object, fact Fact) bool
|
||||
|
||||
// ImportPackageFact retrieves a fact associated with package pkg,
|
||||
// which must be this package or one of its dependencies.
|
||||
// See comments for ImportObjectFact.
|
||||
ImportPackageFact func(pkg *types.Package, fact Fact) bool
|
||||
|
||||
// ExportObjectFact associates a fact of type *T with the obj,
|
||||
// replacing any previous fact of that type.
|
||||
//
|
||||
// ExportObjectFact panics if it is called after the pass is
|
||||
// complete, or if obj does not belong to the package being analyzed.
|
||||
// ExportObjectFact is not concurrency-safe.
|
||||
ExportObjectFact func(obj types.Object, fact Fact)
|
||||
|
||||
// ExportPackageFact associates a fact with the current package.
|
||||
// See comments for ExportObjectFact.
|
||||
ExportPackageFact func(fact Fact)
|
||||
|
||||
// AllPackageFacts returns a new slice containing all package
|
||||
// facts of the analysis's FactTypes in unspecified order.
|
||||
// See comments for AllObjectFacts.
|
||||
AllPackageFacts func() []PackageFact
|
||||
|
||||
// AllObjectFacts returns a new slice containing all object
|
||||
// facts of the analysis's FactTypes in unspecified order.
|
||||
//
|
||||
// The result includes all facts exported by packages
|
||||
// whose symbols are referenced by the current package
|
||||
// (by qualified identifiers or field/method selections).
|
||||
// And it includes all facts exported from the current
|
||||
// package by the current analysis pass.
|
||||
AllObjectFacts func() []ObjectFact
|
||||
|
||||
/* Further fields may be added in future. */
|
||||
}
|
||||
|
||||
// PackageFact is a package together with an associated fact.
|
||||
type PackageFact struct {
|
||||
Package *types.Package
|
||||
Fact Fact
|
||||
}
|
||||
|
||||
// ObjectFact is an object together with an associated fact.
|
||||
type ObjectFact struct {
|
||||
Object types.Object
|
||||
Fact Fact
|
||||
}
|
||||
|
||||
// Reportf is a helper function that reports a Diagnostic using the
|
||||
// specified position and formatted error message.
|
||||
func (pass *Pass) Reportf(pos token.Pos, format string, args ...any) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
pass.Report(Diagnostic{Pos: pos, Message: msg})
|
||||
}
|
||||
|
||||
// The Range interface provides a range. It's equivalent to and satisfied by
|
||||
// ast.Node.
|
||||
type Range interface {
|
||||
Pos() token.Pos // position of first character belonging to the node
|
||||
End() token.Pos // position of first character immediately after the node
|
||||
}
|
||||
|
||||
// ReportRangef is a helper function that reports a Diagnostic using the
|
||||
// range provided. ast.Node values can be passed in as the range because
|
||||
// they satisfy the Range interface.
|
||||
func (pass *Pass) ReportRangef(rng Range, format string, args ...any) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
pass.Report(Diagnostic{Pos: rng.Pos(), End: rng.End(), Message: msg})
|
||||
}
|
||||
|
||||
func (pass *Pass) String() string {
|
||||
return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path())
|
||||
}
|
||||
|
||||
// A Fact is an intermediate fact produced during analysis.
|
||||
//
|
||||
// Each fact is associated with a named declaration (a types.Object) or
|
||||
// with a package as a whole. A single object or package may have
|
||||
// multiple associated facts, but only one of any particular fact type.
|
||||
//
|
||||
// A Fact represents a predicate such as "never returns", but does not
|
||||
// represent the subject of the predicate such as "function F" or "package P".
|
||||
//
|
||||
// Facts may be produced in one analysis pass and consumed by another
|
||||
// analysis pass even if these are in different address spaces.
|
||||
// If package P imports Q, all facts about Q produced during
|
||||
// analysis of that package will be available during later analysis of P.
|
||||
// Facts are analogous to type export data in a build system:
|
||||
// just as export data enables separate compilation of several passes,
|
||||
// facts enable "separate analysis".
|
||||
//
|
||||
// Each pass (a, p) starts with the set of facts produced by the
|
||||
// same analyzer a applied to the packages directly imported by p.
|
||||
// The analysis may add facts to the set, and they may be exported in turn.
|
||||
// An analysis's Run function may retrieve facts by calling
|
||||
// Pass.Import{Object,Package}Fact and update them using
|
||||
// Pass.Export{Object,Package}Fact.
|
||||
//
|
||||
// A fact is logically private to its Analysis. To pass values
|
||||
// between different analyzers, use the results mechanism;
|
||||
// see Analyzer.Requires, Analyzer.ResultType, and Pass.ResultOf.
|
||||
//
|
||||
// A Fact type must be a pointer.
|
||||
// Facts are encoded and decoded using encoding/gob.
|
||||
// A Fact may implement the GobEncoder/GobDecoder interfaces
|
||||
// to customize its encoding. Fact encoding should not fail.
|
||||
//
|
||||
// A Fact should not be modified once exported.
|
||||
type Fact interface {
|
||||
AFact() // dummy method to avoid type errors
|
||||
}
|
||||
|
||||
// A Module describes the module to which a package belongs.
|
||||
type Module struct {
|
||||
Path string // module path
|
||||
Version string // module version ("" if unknown, such as for workspace modules)
|
||||
Replace *Module // replaced by this module
|
||||
Time *time.Time // time version was created
|
||||
Main bool // is this the main module?
|
||||
Indirect bool // is this module only an indirect dependency of main module?
|
||||
Dir string // directory holding files for this module, if any
|
||||
GoMod string // path to go.mod file used when loading this module, if any
|
||||
GoVersion string // go version used in module (e.g. "go1.22.0")
|
||||
Error *ModuleError // error loading module
|
||||
}
|
||||
|
||||
// ModuleError holds errors loading a module.
|
||||
type ModuleError struct {
|
||||
Err string // the error itself
|
||||
}
|
||||
88
vendor/golang.org/x/tools/go/analysis/diagnostic.go
generated
vendored
Normal file
88
vendor/golang.org/x/tools/go/analysis/diagnostic.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package analysis
|
||||
|
||||
import "go/token"
|
||||
|
||||
// A Diagnostic is a message associated with a source location or range.
|
||||
//
|
||||
// An Analyzer may return a variety of diagnostics; the optional Category,
|
||||
// which should be a constant, may be used to classify them.
|
||||
// It is primarily intended to make it easy to look up documentation.
|
||||
//
|
||||
// All Pos values are interpreted relative to Pass.Fset. If End is
|
||||
// provided, the diagnostic is specified to apply to the range between
|
||||
// Pos and End.
|
||||
type Diagnostic struct {
|
||||
Pos token.Pos
|
||||
End token.Pos // optional
|
||||
Category string // optional
|
||||
Message string
|
||||
|
||||
// URL is the optional location of a web page that provides
|
||||
// additional documentation for this diagnostic.
|
||||
//
|
||||
// If URL is empty but a Category is specified, then the
|
||||
// Analysis driver should treat the URL as "#"+Category.
|
||||
//
|
||||
// The URL may be relative. If so, the base URL is that of the
|
||||
// Analyzer that produced the diagnostic;
|
||||
// see https://pkg.go.dev/net/url#URL.ResolveReference.
|
||||
URL string
|
||||
|
||||
// SuggestedFixes is an optional list of fixes to address the
|
||||
// problem described by the diagnostic. Each one represents an
|
||||
// alternative strategy, and should have a distinct and
|
||||
// descriptive message; at most one may be applied.
|
||||
//
|
||||
// Fixes for different diagnostics should be treated as
|
||||
// independent changes to the same baseline file state,
|
||||
// analogous to a set of git commits all with the same parent.
|
||||
// Combining fixes requires resolving any conflicts that
|
||||
// arise, analogous to a git merge.
|
||||
// Any conflicts that remain may be dealt with, depending on
|
||||
// the tool, by discarding fixes, consulting the user, or
|
||||
// aborting the operation.
|
||||
SuggestedFixes []SuggestedFix
|
||||
|
||||
// Related contains optional secondary positions and messages
|
||||
// related to the primary diagnostic.
|
||||
Related []RelatedInformation
|
||||
}
|
||||
|
||||
// RelatedInformation contains information related to a diagnostic.
|
||||
// For example, a diagnostic that flags duplicated declarations of a
|
||||
// variable may include one RelatedInformation per existing
|
||||
// declaration.
|
||||
type RelatedInformation struct {
|
||||
Pos token.Pos
|
||||
End token.Pos // optional
|
||||
Message string
|
||||
}
|
||||
|
||||
// A SuggestedFix is a code change associated with a Diagnostic that a
|
||||
// user can choose to apply to their code. Usually the SuggestedFix is
|
||||
// meant to fix the issue flagged by the diagnostic.
|
||||
//
|
||||
// The TextEdits must not overlap, nor contain edits for other
|
||||
// packages. Edits need not be totally ordered, but the order
|
||||
// determines how insertions at the same point will be applied.
|
||||
type SuggestedFix struct {
|
||||
// A verb phrase describing the fix, to be shown to
|
||||
// a user trying to decide whether to accept it.
|
||||
//
|
||||
// Example: "Remove the surplus argument"
|
||||
Message string
|
||||
TextEdits []TextEdit
|
||||
}
|
||||
|
||||
// A TextEdit represents the replacement of the code between Pos and End with the new text.
|
||||
// Each TextEdit should apply to a single file. End should not be earlier in the file than Pos.
|
||||
type TextEdit struct {
|
||||
// For a pure insertion, End can either be set to Pos or token.NoPos.
|
||||
Pos token.Pos
|
||||
End token.Pos
|
||||
NewText []byte
|
||||
}
|
||||
317
vendor/golang.org/x/tools/go/analysis/doc.go
generated
vendored
Normal file
317
vendor/golang.org/x/tools/go/analysis/doc.go
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package analysis defines the interface between a modular static
|
||||
analysis and an analysis driver program.
|
||||
|
||||
# Background
|
||||
|
||||
A static analysis is a function that inspects a package of Go code and
|
||||
reports a set of diagnostics (typically mistakes in the code), and
|
||||
perhaps produces other results as well, such as suggested refactorings
|
||||
or other facts. An analysis that reports mistakes is informally called a
|
||||
"checker". For example, the printf checker reports mistakes in
|
||||
fmt.Printf format strings.
|
||||
|
||||
A "modular" analysis is one that inspects one package at a time but can
|
||||
save information from a lower-level package and use it when inspecting a
|
||||
higher-level package, analogous to separate compilation in a toolchain.
|
||||
The printf checker is modular: when it discovers that a function such as
|
||||
log.Fatalf delegates to fmt.Printf, it records this fact, and checks
|
||||
calls to that function too, including calls made from another package.
|
||||
|
||||
By implementing a common interface, checkers from a variety of sources
|
||||
can be easily selected, incorporated, and reused in a wide range of
|
||||
driver programs including command-line tools (such as vet), text editors and
|
||||
IDEs, build and test systems (such as go build, Bazel, or Buck), test
|
||||
frameworks, code review tools, code-base indexers (such as SourceGraph),
|
||||
documentation viewers (such as godoc), batch pipelines for large code
|
||||
bases, and so on.
|
||||
|
||||
# Analyzer
|
||||
|
||||
The primary type in the API is [Analyzer]. An Analyzer statically
|
||||
describes an analysis function: its name, documentation, flags,
|
||||
relationship to other analyzers, and of course, its logic.
|
||||
|
||||
To define an analysis, a user declares a (logically constant) variable
|
||||
of type Analyzer. Here is a typical example from one of the analyzers in
|
||||
the go/analysis/passes/ subdirectory:
|
||||
|
||||
package unusedresult
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "unusedresult",
|
||||
Doc: "check for unused results of calls to some functions",
|
||||
Run: run,
|
||||
...
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
...
|
||||
}
|
||||
|
||||
An analysis driver is a program such as vet that runs a set of
|
||||
analyses and prints the diagnostics that they report.
|
||||
The driver program must import the list of Analyzers it needs.
|
||||
Typically each Analyzer resides in a separate package.
|
||||
To add a new Analyzer to an existing driver, add another item to the list:
|
||||
|
||||
import ( "unusedresult"; "nilness"; "printf" )
|
||||
|
||||
var analyses = []*analysis.Analyzer{
|
||||
unusedresult.Analyzer,
|
||||
nilness.Analyzer,
|
||||
printf.Analyzer,
|
||||
}
|
||||
|
||||
A driver may use the name, flags, and documentation to provide on-line
|
||||
help that describes the analyses it performs.
|
||||
The doc comment contains a brief one-line summary,
|
||||
optionally followed by paragraphs of explanation.
|
||||
|
||||
The [Analyzer] type has more fields besides those shown above:
|
||||
|
||||
type Analyzer struct {
|
||||
Name string
|
||||
Doc string
|
||||
Flags flag.FlagSet
|
||||
Run func(*Pass) (interface{}, error)
|
||||
RunDespiteErrors bool
|
||||
ResultType reflect.Type
|
||||
Requires []*Analyzer
|
||||
FactTypes []Fact
|
||||
}
|
||||
|
||||
The Flags field declares a set of named (global) flag variables that
|
||||
control analysis behavior. Unlike vet, analysis flags are not declared
|
||||
directly in the command line FlagSet; it is up to the driver to set the
|
||||
flag variables. A driver for a single analysis, a, might expose its flag
|
||||
f directly on the command line as -f, whereas a driver for multiple
|
||||
analyses might prefix the flag name by the analysis name (-a.f) to avoid
|
||||
ambiguity. An IDE might expose the flags through a graphical interface,
|
||||
and a batch pipeline might configure them from a config file.
|
||||
See the "findcall" analyzer for an example of flags in action.
|
||||
|
||||
The RunDespiteErrors flag indicates whether the analysis is equipped to
|
||||
handle ill-typed code. If not, the driver will skip the analysis if
|
||||
there were parse or type errors.
|
||||
The optional ResultType field specifies the type of the result value
|
||||
computed by this analysis and made available to other analyses.
|
||||
The Requires field specifies a list of analyses upon which
|
||||
this one depends and whose results it may access, and it constrains the
|
||||
order in which a driver may run analyses.
|
||||
The FactTypes field is discussed in the section on Modularity.
|
||||
The analysis package provides a Validate function to perform basic
|
||||
sanity checks on an Analyzer, such as that its Requires graph is
|
||||
acyclic, its fact and result types are unique, and so on.
|
||||
|
||||
Finally, the Run field contains a function to be called by the driver to
|
||||
execute the analysis on a single package. The driver passes it an
|
||||
instance of the Pass type.
|
||||
|
||||
# Pass
|
||||
|
||||
A [Pass] describes a single unit of work: the application of a particular
|
||||
Analyzer to a particular package of Go code.
|
||||
The Pass provides information to the Analyzer's Run function about the
|
||||
package being analyzed, and provides operations to the Run function for
|
||||
reporting diagnostics and other information back to the driver.
|
||||
|
||||
type Pass struct {
|
||||
Fset *token.FileSet
|
||||
Files []*ast.File
|
||||
OtherFiles []string
|
||||
IgnoredFiles []string
|
||||
Pkg *types.Package
|
||||
TypesInfo *types.Info
|
||||
ResultOf map[*Analyzer]interface{}
|
||||
Report func(Diagnostic)
|
||||
...
|
||||
}
|
||||
|
||||
The Fset, Files, Pkg, and TypesInfo fields provide the syntax trees,
|
||||
type information, and source positions for a single package of Go code.
|
||||
|
||||
The OtherFiles field provides the names of non-Go
|
||||
files such as assembly that are part of this package.
|
||||
Similarly, the IgnoredFiles field provides the names of Go and non-Go
|
||||
source files that are not part of this package with the current build
|
||||
configuration but may be part of other build configurations.
|
||||
The contents of these files may be read using Pass.ReadFile;
|
||||
see the "asmdecl" or "buildtags" analyzers for examples of loading
|
||||
non-Go files and reporting diagnostics against them.
|
||||
|
||||
The ResultOf field provides the results computed by the analyzers
|
||||
required by this one, as expressed in its Analyzer.Requires field. The
|
||||
driver runs the required analyzers first and makes their results
|
||||
available in this map. Each Analyzer must return a value of the type
|
||||
described in its Analyzer.ResultType field.
|
||||
For example, the "ctrlflow" analyzer returns a *ctrlflow.CFGs, which
|
||||
provides a control-flow graph for each function in the package (see
|
||||
golang.org/x/tools/go/cfg); the "inspect" analyzer returns a value that
|
||||
enables other Analyzers to traverse the syntax trees of the package more
|
||||
efficiently; and the "buildssa" analyzer constructs an SSA-form
|
||||
intermediate representation.
|
||||
Each of these Analyzers extends the capabilities of later Analyzers
|
||||
without adding a dependency to the core API, so an analysis tool pays
|
||||
only for the extensions it needs.
|
||||
|
||||
The Report function emits a diagnostic, a message associated with a
|
||||
source position. For most analyses, diagnostics are their primary
|
||||
result.
|
||||
For convenience, Pass provides a helper method, Reportf, to report a new
|
||||
diagnostic by formatting a string.
|
||||
Diagnostic is defined as:
|
||||
|
||||
type Diagnostic struct {
|
||||
Pos token.Pos
|
||||
Category string // optional
|
||||
Message string
|
||||
}
|
||||
|
||||
The optional Category field is a short identifier that classifies the
|
||||
kind of message when an analysis produces several kinds of diagnostic.
|
||||
|
||||
The [Diagnostic] struct does not have a field to indicate its severity
|
||||
because opinions about the relative importance of Analyzers and their
|
||||
diagnostics vary widely among users. The design of this framework does
|
||||
not hold each Analyzer responsible for identifying the severity of its
|
||||
diagnostics. Instead, we expect that drivers will allow the user to
|
||||
customize the filtering and prioritization of diagnostics based on the
|
||||
producing Analyzer and optional Category, according to the user's
|
||||
preferences.
|
||||
|
||||
Most Analyzers inspect typed Go syntax trees, but a few, such as asmdecl
|
||||
and buildtag, inspect the raw text of Go source files or even non-Go
|
||||
files such as assembly. To report a diagnostic against a line of a
|
||||
raw text file, use the following sequence:
|
||||
|
||||
content, err := pass.ReadFile(filename)
|
||||
if err != nil { ... }
|
||||
tf := fset.AddFile(filename, -1, len(content))
|
||||
tf.SetLinesForContent(content)
|
||||
...
|
||||
pass.Reportf(tf.LineStart(line), "oops")
|
||||
|
||||
# Modular analysis with Facts
|
||||
|
||||
To improve efficiency and scalability, large programs are routinely
|
||||
built using separate compilation: units of the program are compiled
|
||||
separately, and recompiled only when one of their dependencies changes;
|
||||
independent modules may be compiled in parallel. The same technique may
|
||||
be applied to static analyses, for the same benefits. Such analyses are
|
||||
described as "modular".
|
||||
|
||||
A compiler’s type checker is an example of a modular static analysis.
|
||||
Many other checkers we would like to apply to Go programs can be
|
||||
understood as alternative or non-standard type systems. For example,
|
||||
vet's printf checker infers whether a function has the "printf wrapper"
|
||||
type, and it applies stricter checks to calls of such functions. In
|
||||
addition, it records which functions are printf wrappers for use by
|
||||
later analysis passes to identify other printf wrappers by induction.
|
||||
A result such as “f is a printf wrapper” that is not interesting by
|
||||
itself but serves as a stepping stone to an interesting result (such as
|
||||
a diagnostic) is called a [Fact].
|
||||
|
||||
The analysis API allows an analysis to define new types of facts, to
|
||||
associate facts of these types with objects (named entities) declared
|
||||
within the current package, or with the package as a whole, and to query
|
||||
for an existing fact of a given type associated with an object or
|
||||
package.
|
||||
|
||||
An Analyzer that uses facts must declare their types:
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "printf",
|
||||
FactTypes: []analysis.Fact{new(isWrapper)},
|
||||
...
|
||||
}
|
||||
|
||||
type isWrapper struct{} // => *types.Func f “is a printf wrapper”
|
||||
|
||||
The driver program ensures that facts for a pass’s dependencies are
|
||||
generated before analyzing the package and is responsible for propagating
|
||||
facts from one package to another, possibly across address spaces.
|
||||
Consequently, Facts must be serializable. The API requires that drivers
|
||||
use the gob encoding, an efficient, robust, self-describing binary
|
||||
protocol. A fact type may implement the GobEncoder/GobDecoder interfaces
|
||||
if the default encoding is unsuitable. Facts should be stateless.
|
||||
Because serialized facts may appear within build outputs, the gob encoding
|
||||
of a fact must be deterministic, to avoid spurious cache misses in
|
||||
build systems that use content-addressable caches.
|
||||
The driver makes a single call to the gob encoder for all facts
|
||||
exported by a given analysis pass, so that the topology of
|
||||
shared data structures referenced by multiple facts is preserved.
|
||||
|
||||
The Pass type has functions to import and export facts,
|
||||
associated either with an object or with a package:
|
||||
|
||||
type Pass struct {
|
||||
...
|
||||
ExportObjectFact func(types.Object, Fact)
|
||||
ImportObjectFact func(types.Object, Fact) bool
|
||||
|
||||
ExportPackageFact func(fact Fact)
|
||||
ImportPackageFact func(*types.Package, Fact) bool
|
||||
}
|
||||
|
||||
An Analyzer may only export facts associated with the current package or
|
||||
its objects, though it may import facts from any package or object that
|
||||
is an import dependency of the current package.
|
||||
|
||||
Conceptually, ExportObjectFact(obj, fact) inserts fact into a hidden map keyed by
|
||||
the pair (obj, TypeOf(fact)), and the ImportObjectFact function
|
||||
retrieves the entry from this map and copies its value into the variable
|
||||
pointed to by fact. This scheme assumes that the concrete type of fact
|
||||
is a pointer; this assumption is checked by the Validate function.
|
||||
See the "printf" analyzer for an example of object facts in action.
|
||||
|
||||
Some driver implementations (such as those based on Bazel and Blaze) do
|
||||
not currently apply analyzers to packages of the standard library.
|
||||
Therefore, for best results, analyzer authors should not rely on
|
||||
analysis facts being available for standard packages.
|
||||
For example, although the printf checker is capable of deducing during
|
||||
analysis of the log package that log.Printf is a printf wrapper,
|
||||
this fact is built in to the analyzer so that it correctly checks
|
||||
calls to log.Printf even when run in a driver that does not apply
|
||||
it to standard packages. We would like to remove this limitation in future.
|
||||
|
||||
# Testing an Analyzer
|
||||
|
||||
The analysistest subpackage provides utilities for testing an Analyzer.
|
||||
In a few lines of code, it is possible to run an analyzer on a package
|
||||
of testdata files and check that it reported all the expected
|
||||
diagnostics and facts (and no more). Expectations are expressed using
|
||||
"// want ..." comments in the input code.
|
||||
|
||||
# Standalone commands
|
||||
|
||||
Analyzers are provided in the form of packages that a driver program is
|
||||
expected to import. The vet command imports a set of several analyzers,
|
||||
but users may wish to define their own analysis commands that perform
|
||||
additional checks. To simplify the task of creating an analysis command,
|
||||
either for a single analyzer or for a whole suite, we provide the
|
||||
singlechecker and multichecker subpackages.
|
||||
|
||||
The singlechecker package provides the main function for a command that
|
||||
runs one analyzer. By convention, each analyzer such as
|
||||
go/analysis/passes/findcall should be accompanied by a singlechecker-based
|
||||
command such as go/analysis/passes/findcall/cmd/findcall, defined in its
|
||||
entirety as:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/analysis/passes/findcall"
|
||||
"golang.org/x/tools/go/analysis/singlechecker"
|
||||
)
|
||||
|
||||
func main() { singlechecker.Main(findcall.Analyzer) }
|
||||
|
||||
A tool that provides multiple analyzers can use multichecker in a
|
||||
similar way, giving it the list of Analyzers.
|
||||
*/
|
||||
package analysis
|
||||
278
vendor/golang.org/x/tools/go/analysis/passes/ctrlflow/ctrlflow.go
generated
vendored
Normal file
278
vendor/golang.org/x/tools/go/analysis/passes/ctrlflow/ctrlflow.go
generated
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ctrlflow is an analysis that provides a syntactic
|
||||
// control-flow graph (CFG) for the body of a function.
|
||||
// It records whether a function cannot return.
|
||||
// By itself, it does not report any diagnostics.
|
||||
package ctrlflow
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"log"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"golang.org/x/tools/go/cfg"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
)
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "ctrlflow",
|
||||
Doc: "build a control-flow graph",
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ctrlflow",
|
||||
Run: run,
|
||||
ResultType: reflect.TypeFor[*CFGs](),
|
||||
FactTypes: []analysis.Fact{new(noReturn)},
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
}
|
||||
|
||||
// noReturn is a fact indicating that a function does not return.
|
||||
type noReturn struct{}
|
||||
|
||||
func (*noReturn) AFact() {}
|
||||
|
||||
func (*noReturn) String() string { return "noReturn" }
|
||||
|
||||
// A CFGs holds the control-flow graphs
|
||||
// for all the functions of the current package.
|
||||
type CFGs struct {
|
||||
defs map[*ast.Ident]types.Object // from Pass.TypesInfo.Defs
|
||||
funcDecls map[*types.Func]*declInfo
|
||||
funcLits map[*ast.FuncLit]*litInfo
|
||||
noReturn map[*types.Func]bool // functions lacking a reachable return statement
|
||||
pass *analysis.Pass // transient; nil after construction
|
||||
}
|
||||
|
||||
// NoReturn reports whether the specified control-flow graph cannot return normally.
|
||||
//
|
||||
// It is defined for at least all function symbols that appear as the static callee of a
|
||||
// CallExpr in the current package, even if the callee was imported from a dependency.
|
||||
//
|
||||
// The result may incorporate interprocedural information based on induction of
|
||||
// the "no return" property over the static call graph within the package.
|
||||
// For example, if f simply calls g and g always calls os.Exit, then both f and g may
|
||||
// be deemed never to return.
|
||||
func (c *CFGs) NoReturn(fn *types.Func) bool {
|
||||
return c.noReturn[fn]
|
||||
}
|
||||
|
||||
// CFGs has two maps: funcDecls for named functions and funcLits for
|
||||
// unnamed ones. Unlike funcLits, the funcDecls map is not keyed by its
|
||||
// syntax node, *ast.FuncDecl, because callMayReturn needs to do a
|
||||
// look-up by *types.Func, and you can get from an *ast.FuncDecl to a
|
||||
// *types.Func but not the other way.
|
||||
|
||||
type declInfo struct {
|
||||
decl *ast.FuncDecl
|
||||
cfg *cfg.CFG // iff decl.Body != nil
|
||||
started bool // to break cycles
|
||||
}
|
||||
|
||||
type litInfo struct {
|
||||
cfg *cfg.CFG
|
||||
noReturn bool // (currently unused)
|
||||
}
|
||||
|
||||
// FuncDecl returns the control-flow graph for a named function.
|
||||
// It returns nil if decl.Body==nil.
|
||||
func (c *CFGs) FuncDecl(decl *ast.FuncDecl) *cfg.CFG {
|
||||
if decl.Body == nil {
|
||||
return nil
|
||||
}
|
||||
fn := c.defs[decl.Name].(*types.Func)
|
||||
return c.funcDecls[fn].cfg
|
||||
}
|
||||
|
||||
// FuncLit returns the control-flow graph for a literal function.
|
||||
func (c *CFGs) FuncLit(lit *ast.FuncLit) *cfg.CFG {
|
||||
return c.funcLits[lit].cfg
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
|
||||
// Because CFG construction consumes and produces noReturn
|
||||
// facts, CFGs for exported FuncDecls must be built before 'run'
|
||||
// returns; we cannot construct them lazily.
|
||||
// (We could build CFGs for FuncLits lazily,
|
||||
// but the benefit is marginal.)
|
||||
|
||||
// Pass 1. Map types.Funcs to ast.FuncDecls in this package.
|
||||
funcDecls := make(map[*types.Func]*declInfo) // functions and methods
|
||||
funcLits := make(map[*ast.FuncLit]*litInfo)
|
||||
|
||||
var decls []*types.Func // keys(funcDecls), in order
|
||||
var lits []*ast.FuncLit // keys(funcLits), in order
|
||||
|
||||
nodeFilter := []ast.Node{
|
||||
(*ast.FuncDecl)(nil),
|
||||
(*ast.FuncLit)(nil),
|
||||
}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
switch n := n.(type) {
|
||||
case *ast.FuncDecl:
|
||||
// Type information may be incomplete.
|
||||
if fn, ok := pass.TypesInfo.Defs[n.Name].(*types.Func); ok {
|
||||
funcDecls[fn] = &declInfo{decl: n}
|
||||
decls = append(decls, fn)
|
||||
}
|
||||
case *ast.FuncLit:
|
||||
funcLits[n] = new(litInfo)
|
||||
lits = append(lits, n)
|
||||
}
|
||||
})
|
||||
|
||||
c := &CFGs{
|
||||
defs: pass.TypesInfo.Defs,
|
||||
funcDecls: funcDecls,
|
||||
funcLits: funcLits,
|
||||
noReturn: make(map[*types.Func]bool),
|
||||
pass: pass,
|
||||
}
|
||||
|
||||
// Pass 2. Build CFGs.
|
||||
|
||||
// Build CFGs for named functions.
|
||||
// Cycles in the static call graph are broken
|
||||
// arbitrarily but deterministically.
|
||||
// We create noReturn facts as discovered.
|
||||
for _, fn := range decls {
|
||||
c.buildDecl(fn, funcDecls[fn])
|
||||
}
|
||||
|
||||
// Build CFGs for literal functions.
|
||||
// These aren't relevant to facts (since they aren't named)
|
||||
// but are required for the CFGs.FuncLit API.
|
||||
for _, lit := range lits {
|
||||
li := funcLits[lit]
|
||||
if li.cfg == nil {
|
||||
li.cfg = cfg.New(lit.Body, c.callMayReturn)
|
||||
if li.cfg.NoReturn() {
|
||||
li.noReturn = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All CFGs are now built.
|
||||
c.pass = nil
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// di.cfg may be nil on return.
|
||||
func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) {
|
||||
// buildDecl may call itself recursively for the same function,
|
||||
// because cfg.New is passed the callMayReturn method, which
|
||||
// builds the CFG of the callee, leading to recursion.
|
||||
// The buildDecl call tree thus resembles the static call graph.
|
||||
// We mark each node when we start working on it to break cycles.
|
||||
|
||||
if di.started {
|
||||
return // break cycle
|
||||
}
|
||||
di.started = true
|
||||
|
||||
noreturn, known := knownIntrinsic(fn)
|
||||
if !known {
|
||||
if di.decl.Body != nil {
|
||||
di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
|
||||
if di.cfg.NoReturn() {
|
||||
noreturn = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if noreturn {
|
||||
c.pass.ExportObjectFact(fn, new(noReturn))
|
||||
c.noReturn[fn] = true
|
||||
}
|
||||
|
||||
// debugging
|
||||
if false {
|
||||
log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), noreturn)
|
||||
}
|
||||
}
|
||||
|
||||
// callMayReturn reports whether the called function may return.
|
||||
// It is passed to the CFG builder.
|
||||
func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
|
||||
if id, ok := call.Fun.(*ast.Ident); ok && c.pass.TypesInfo.Uses[id] == panicBuiltin {
|
||||
return false // panic never returns
|
||||
}
|
||||
|
||||
// Is this a static call? Also includes static functions
|
||||
// parameterized by a type. Such functions may or may not
|
||||
// return depending on the parameter type, but in some
|
||||
// cases the answer is definite. We let ctrlflow figure
|
||||
// that out.
|
||||
fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
|
||||
if fn == nil {
|
||||
return true // callee not statically known; be conservative
|
||||
}
|
||||
|
||||
// Function or method declared in this package?
|
||||
if di, ok := c.funcDecls[fn]; ok {
|
||||
c.buildDecl(fn, di)
|
||||
return !c.noReturn[fn]
|
||||
}
|
||||
|
||||
// Not declared in this package.
|
||||
// Is there a fact from another package?
|
||||
if c.pass.ImportObjectFact(fn, new(noReturn)) {
|
||||
c.noReturn[fn] = true
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
|
||||
|
||||
// knownIntrinsic reports whether a function intrinsically never
|
||||
// returns because it stops execution of the calling thread, or does
|
||||
// in fact return, contrary to its apparent body, because it is
|
||||
// handled specially by the compiler.
|
||||
//
|
||||
// It is the base case in the recursion.
|
||||
func knownIntrinsic(fn *types.Func) (noreturn, known bool) {
|
||||
// Add functions here as the need arises, but don't allocate memory.
|
||||
|
||||
// Functions known intrinsically never to return.
|
||||
if typesinternal.IsFunctionNamed(fn, "syscall", "Exit", "ExitProcess", "ExitThread") ||
|
||||
typesinternal.IsFunctionNamed(fn, "runtime", "Goexit", "fatalthrow", "fatalpanic", "exit") ||
|
||||
// Following staticcheck (see go/ir/exits.go) we include functions
|
||||
// in several popular logging packages whose no-return status is
|
||||
// beyond the analysis to infer.
|
||||
// TODO(adonovan): make this list extensible.
|
||||
typesinternal.IsMethodNamed(fn, "go.uber.org/zap", "Logger", "Fatal", "Panic") ||
|
||||
typesinternal.IsMethodNamed(fn, "go.uber.org/zap", "SugaredLogger", "Fatal", "Fatalw", "Fatalf", "Panic", "Panicw", "Panicf") ||
|
||||
typesinternal.IsMethodNamed(fn, "github.com/sirupsen/logrus", "Logger", "Exit", "Panic", "Panicf", "Panicln") ||
|
||||
typesinternal.IsMethodNamed(fn, "github.com/sirupsen/logrus", "Entry", "Panicf", "Panicln") ||
|
||||
typesinternal.IsFunctionNamed(fn, "k8s.io/klog", "Exit", "ExitDepth", "Exitf", "Exitln", "Fatal", "FatalDepth", "Fatalf", "Fatalln") ||
|
||||
typesinternal.IsFunctionNamed(fn, "k8s.io/klog/v2", "Exit", "ExitDepth", "Exitf", "Exitln", "Fatal", "FatalDepth", "Fatalf", "Fatalln") {
|
||||
return true, true
|
||||
}
|
||||
|
||||
// Compiler intrinsics known to return, contrary to
|
||||
// what analysis of the function body would conclude.
|
||||
//
|
||||
// Not all such intrinsics must be listed here: ctrlflow
|
||||
// considers any function called for its value--such as
|
||||
// crypto/internal/constanttime.bool2Uint8--to potentially
|
||||
// return; only functions called as a statement, for effects,
|
||||
// are no-return candidates.
|
||||
//
|
||||
// Unfortunately this does sometimes mean peering into internals.
|
||||
// Where possible, use the nearest enclosing public API function.
|
||||
if typesinternal.IsFunctionNamed(fn, "internal/abi", "EscapeNonString") ||
|
||||
typesinternal.IsFunctionNamed(fn, "hash/maphash", "Comparable") {
|
||||
return false, true
|
||||
}
|
||||
|
||||
return // unknown
|
||||
}
|
||||
49
vendor/golang.org/x/tools/go/analysis/passes/inspect/inspect.go
generated
vendored
Normal file
49
vendor/golang.org/x/tools/go/analysis/passes/inspect/inspect.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package inspect defines an Analyzer that provides an AST inspector
|
||||
// (golang.org/x/tools/go/ast/inspector.Inspector) for the syntax trees
|
||||
// of a package. It is only a building block for other analyzers.
|
||||
//
|
||||
// Example of use in another analysis:
|
||||
//
|
||||
// import (
|
||||
// "golang.org/x/tools/go/analysis"
|
||||
// "golang.org/x/tools/go/analysis/passes/inspect"
|
||||
// "golang.org/x/tools/go/ast/inspector"
|
||||
// )
|
||||
//
|
||||
// var Analyzer = &analysis.Analyzer{
|
||||
// ...
|
||||
// Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
// }
|
||||
//
|
||||
// func run(pass *analysis.Pass) (interface{}, error) {
|
||||
// inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
// inspect.Preorder(nil, func(n ast.Node) {
|
||||
// ...
|
||||
// })
|
||||
// return nil, nil
|
||||
// }
|
||||
package inspect
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "inspect",
|
||||
Doc: "optimize AST traversal for later passes",
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inspect",
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
ResultType: reflect.TypeFor[*inspector.Inspector](),
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
return inspector.New(pass.Files), nil
|
||||
}
|
||||
137
vendor/golang.org/x/tools/go/analysis/validate.go
generated
vendored
Normal file
137
vendor/golang.org/x/tools/go/analysis/validate.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package analysis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Validate reports an error if any of the analyzers are misconfigured.
|
||||
// Checks include:
|
||||
// that the name is a valid identifier;
|
||||
// that the Doc is not empty;
|
||||
// that the Run is non-nil;
|
||||
// that the Requires graph is acyclic;
|
||||
// that analyzer fact types are unique;
|
||||
// that each fact type is a pointer.
|
||||
//
|
||||
// Analyzer names need not be unique, though this may be confusing.
|
||||
func Validate(analyzers []*Analyzer) error {
|
||||
// Map each fact type to its sole generating analyzer.
|
||||
factTypes := make(map[reflect.Type]*Analyzer)
|
||||
|
||||
// Traverse the Requires graph, depth first.
|
||||
const (
|
||||
white = iota
|
||||
grey
|
||||
black
|
||||
finished
|
||||
)
|
||||
color := make(map[*Analyzer]uint8)
|
||||
var visit func(a *Analyzer) error
|
||||
visit = func(a *Analyzer) error {
|
||||
if a == nil {
|
||||
return fmt.Errorf("nil *Analyzer")
|
||||
}
|
||||
if color[a] == white {
|
||||
color[a] = grey
|
||||
|
||||
// names
|
||||
if !validIdent(a.Name) {
|
||||
return fmt.Errorf("invalid analyzer name %q", a)
|
||||
}
|
||||
|
||||
if a.Doc == "" {
|
||||
return fmt.Errorf("analyzer %q is undocumented", a)
|
||||
}
|
||||
|
||||
if a.Run == nil {
|
||||
return fmt.Errorf("analyzer %q has nil Run", a)
|
||||
}
|
||||
// fact types
|
||||
for _, f := range a.FactTypes {
|
||||
if f == nil {
|
||||
return fmt.Errorf("analyzer %s has nil FactType", a)
|
||||
}
|
||||
t := reflect.TypeOf(f)
|
||||
if prev := factTypes[t]; prev != nil {
|
||||
return fmt.Errorf("fact type %s registered by two analyzers: %v, %v",
|
||||
t, a, prev)
|
||||
}
|
||||
if t.Kind() != reflect.Pointer {
|
||||
return fmt.Errorf("%s: fact type %s is not a pointer", a, t)
|
||||
}
|
||||
factTypes[t] = a
|
||||
}
|
||||
|
||||
// recursion
|
||||
for _, req := range a.Requires {
|
||||
if err := visit(req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
color[a] = black
|
||||
}
|
||||
|
||||
if color[a] == grey {
|
||||
stack := []*Analyzer{a}
|
||||
inCycle := map[string]bool{}
|
||||
for len(stack) > 0 {
|
||||
current := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
if color[current] == grey && !inCycle[current.Name] {
|
||||
inCycle[current.Name] = true
|
||||
stack = append(stack, current.Requires...)
|
||||
}
|
||||
}
|
||||
return &CycleInRequiresGraphError{AnalyzerNames: inCycle}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
for _, a := range analyzers {
|
||||
if err := visit(a); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Reject duplicates among analyzers.
|
||||
// Precondition: color[a] == black.
|
||||
// Postcondition: color[a] == finished.
|
||||
for _, a := range analyzers {
|
||||
if color[a] == finished {
|
||||
return fmt.Errorf("duplicate analyzer: %s", a.Name)
|
||||
}
|
||||
color[a] = finished
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validIdent(name string) bool {
|
||||
for i, r := range name {
|
||||
if !(r == '_' || unicode.IsLetter(r) || i > 0 && unicode.IsDigit(r)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return name != ""
|
||||
}
|
||||
|
||||
type CycleInRequiresGraphError struct {
|
||||
AnalyzerNames map[string]bool
|
||||
}
|
||||
|
||||
func (e *CycleInRequiresGraphError) Error() string {
|
||||
var b strings.Builder
|
||||
b.WriteString("cycle detected involving the following analyzers:")
|
||||
for n := range e.AnalyzerNames {
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(n)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
519
vendor/golang.org/x/tools/go/cfg/builder.go
generated
vendored
Normal file
519
vendor/golang.org/x/tools/go/cfg/builder.go
generated
vendored
Normal file
@@ -0,0 +1,519 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cfg
|
||||
|
||||
// This file implements the CFG construction pass.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
type builder struct {
|
||||
blocks []*Block
|
||||
mayReturn func(*ast.CallExpr) bool
|
||||
current *Block
|
||||
lblocks map[string]*lblock // labeled blocks
|
||||
targets *targets // linked stack of branch targets
|
||||
}
|
||||
|
||||
func (b *builder) stmt(_s ast.Stmt) {
|
||||
// The label of the current statement. If non-nil, its _goto
|
||||
// target is always set; its _break and _continue are set only
|
||||
// within the body of switch/typeswitch/select/for/range.
|
||||
// It is effectively an additional default-nil parameter of stmt().
|
||||
var label *lblock
|
||||
start:
|
||||
switch s := _s.(type) {
|
||||
case *ast.BadStmt,
|
||||
*ast.SendStmt,
|
||||
*ast.IncDecStmt,
|
||||
*ast.GoStmt,
|
||||
*ast.EmptyStmt,
|
||||
*ast.AssignStmt:
|
||||
// No effect on control flow.
|
||||
b.add(s)
|
||||
|
||||
case *ast.DeferStmt:
|
||||
b.add(s)
|
||||
// Assume conservatively that this behaves like:
|
||||
// defer func() { recover() }
|
||||
// so any subsequent panic may act like a return.
|
||||
b.current.returns = true
|
||||
|
||||
case *ast.ExprStmt:
|
||||
b.add(s)
|
||||
if call, ok := s.X.(*ast.CallExpr); ok && !b.mayReturn(call) {
|
||||
// Calls to panic, os.Exit, etc, never return.
|
||||
b.current = b.newBlock(KindUnreachable, s)
|
||||
}
|
||||
|
||||
case *ast.DeclStmt:
|
||||
// Treat each var ValueSpec as a separate statement.
|
||||
d := s.Decl.(*ast.GenDecl)
|
||||
if d.Tok == token.VAR {
|
||||
for _, spec := range d.Specs {
|
||||
if spec, ok := spec.(*ast.ValueSpec); ok {
|
||||
b.add(spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
label = b.labeledBlock(s.Label, s)
|
||||
b.jump(label._goto)
|
||||
b.current = label._goto
|
||||
_s = s.Stmt
|
||||
goto start // effectively: tailcall stmt(g, s.Stmt, label)
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
b.current.returns = true
|
||||
b.add(s)
|
||||
b.current = b.newBlock(KindUnreachable, s)
|
||||
|
||||
case *ast.BranchStmt:
|
||||
b.branchStmt(s)
|
||||
|
||||
case *ast.BlockStmt:
|
||||
b.stmtList(s.List)
|
||||
|
||||
case *ast.IfStmt:
|
||||
if s.Init != nil {
|
||||
b.stmt(s.Init)
|
||||
}
|
||||
then := b.newBlock(KindIfThen, s)
|
||||
done := b.newBlock(KindIfDone, s)
|
||||
_else := done
|
||||
if s.Else != nil {
|
||||
_else = b.newBlock(KindIfElse, s)
|
||||
}
|
||||
b.add(s.Cond)
|
||||
b.ifelse(then, _else)
|
||||
b.current = then
|
||||
b.stmt(s.Body)
|
||||
b.jump(done)
|
||||
|
||||
if s.Else != nil {
|
||||
b.current = _else
|
||||
b.stmt(s.Else)
|
||||
b.jump(done)
|
||||
}
|
||||
|
||||
b.current = done
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
b.switchStmt(s, label)
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
b.typeSwitchStmt(s, label)
|
||||
|
||||
case *ast.SelectStmt:
|
||||
b.selectStmt(s, label)
|
||||
|
||||
case *ast.ForStmt:
|
||||
b.forStmt(s, label)
|
||||
|
||||
case *ast.RangeStmt:
|
||||
b.rangeStmt(s, label)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected statement kind: %T", s))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) stmtList(list []ast.Stmt) {
|
||||
for _, s := range list {
|
||||
b.stmt(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) branchStmt(s *ast.BranchStmt) {
|
||||
var block *Block
|
||||
switch s.Tok {
|
||||
case token.BREAK:
|
||||
if s.Label != nil {
|
||||
if lb := b.labeledBlock(s.Label, nil); lb != nil {
|
||||
block = lb._break
|
||||
}
|
||||
} else {
|
||||
for t := b.targets; t != nil && block == nil; t = t.tail {
|
||||
block = t._break
|
||||
}
|
||||
}
|
||||
|
||||
case token.CONTINUE:
|
||||
if s.Label != nil {
|
||||
if lb := b.labeledBlock(s.Label, nil); lb != nil {
|
||||
block = lb._continue
|
||||
}
|
||||
} else {
|
||||
for t := b.targets; t != nil && block == nil; t = t.tail {
|
||||
block = t._continue
|
||||
}
|
||||
}
|
||||
|
||||
case token.FALLTHROUGH:
|
||||
for t := b.targets; t != nil && block == nil; t = t.tail {
|
||||
block = t._fallthrough
|
||||
}
|
||||
|
||||
case token.GOTO:
|
||||
if s.Label != nil {
|
||||
block = b.labeledBlock(s.Label, nil)._goto
|
||||
}
|
||||
}
|
||||
if block == nil { // ill-typed (e.g. undefined label)
|
||||
block = b.newBlock(KindUnreachable, s)
|
||||
}
|
||||
b.jump(block)
|
||||
b.current = b.newBlock(KindUnreachable, s)
|
||||
}
|
||||
|
||||
func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) {
|
||||
if s.Init != nil {
|
||||
b.stmt(s.Init)
|
||||
}
|
||||
if s.Tag != nil {
|
||||
b.add(s.Tag)
|
||||
}
|
||||
done := b.newBlock(KindSwitchDone, s)
|
||||
if label != nil {
|
||||
label._break = done
|
||||
}
|
||||
// We pull the default case (if present) down to the end.
|
||||
// But each fallthrough label must point to the next
|
||||
// body block in source order, so we preallocate a
|
||||
// body block (fallthru) for the next case.
|
||||
// Unfortunately this makes for a confusing block order.
|
||||
var defaultBody *[]ast.Stmt
|
||||
var defaultFallthrough *Block
|
||||
var fallthru, defaultBlock *Block
|
||||
ncases := len(s.Body.List)
|
||||
for i, clause := range s.Body.List {
|
||||
body := fallthru
|
||||
if body == nil {
|
||||
body = b.newBlock(KindSwitchCaseBody, clause) // first case only
|
||||
}
|
||||
|
||||
// Preallocate body block for the next case.
|
||||
fallthru = done
|
||||
if i+1 < ncases {
|
||||
fallthru = b.newBlock(KindSwitchCaseBody, s.Body.List[i+1])
|
||||
}
|
||||
|
||||
cc := clause.(*ast.CaseClause)
|
||||
if cc.List == nil {
|
||||
// Default case.
|
||||
defaultBody = &cc.Body
|
||||
defaultFallthrough = fallthru
|
||||
defaultBlock = body
|
||||
continue
|
||||
}
|
||||
|
||||
var nextCond *Block
|
||||
for _, cond := range cc.List {
|
||||
nextCond = b.newBlock(KindSwitchNextCase, cc)
|
||||
b.add(cond) // one half of the tag==cond condition
|
||||
b.ifelse(body, nextCond)
|
||||
b.current = nextCond
|
||||
}
|
||||
b.current = body
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
_fallthrough: fallthru,
|
||||
}
|
||||
b.stmtList(cc.Body)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(done)
|
||||
b.current = nextCond
|
||||
}
|
||||
if defaultBlock != nil {
|
||||
b.jump(defaultBlock)
|
||||
b.current = defaultBlock
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
_fallthrough: defaultFallthrough,
|
||||
}
|
||||
b.stmtList(*defaultBody)
|
||||
b.targets = b.targets.tail
|
||||
}
|
||||
b.jump(done)
|
||||
b.current = done
|
||||
}
|
||||
|
||||
func (b *builder) typeSwitchStmt(s *ast.TypeSwitchStmt, label *lblock) {
|
||||
if s.Init != nil {
|
||||
b.stmt(s.Init)
|
||||
}
|
||||
if s.Assign != nil {
|
||||
b.add(s.Assign)
|
||||
}
|
||||
|
||||
done := b.newBlock(KindSwitchDone, s)
|
||||
if label != nil {
|
||||
label._break = done
|
||||
}
|
||||
var default_ *ast.CaseClause
|
||||
for _, clause := range s.Body.List {
|
||||
cc := clause.(*ast.CaseClause)
|
||||
if cc.List == nil {
|
||||
default_ = cc
|
||||
continue
|
||||
}
|
||||
body := b.newBlock(KindSwitchCaseBody, cc)
|
||||
var next *Block
|
||||
for _, casetype := range cc.List {
|
||||
next = b.newBlock(KindSwitchNextCase, cc)
|
||||
// casetype is a type, so don't call b.add(casetype).
|
||||
// This block logically contains a type assertion,
|
||||
// x.(casetype), but it's unclear how to represent x.
|
||||
_ = casetype
|
||||
b.ifelse(body, next)
|
||||
b.current = next
|
||||
}
|
||||
b.current = body
|
||||
b.typeCaseBody(cc, done)
|
||||
b.current = next
|
||||
}
|
||||
if default_ != nil {
|
||||
b.typeCaseBody(default_, done)
|
||||
} else {
|
||||
b.jump(done)
|
||||
}
|
||||
b.current = done
|
||||
}
|
||||
|
||||
func (b *builder) typeCaseBody(cc *ast.CaseClause, done *Block) {
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
}
|
||||
b.stmtList(cc.Body)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(done)
|
||||
}
|
||||
|
||||
func (b *builder) selectStmt(s *ast.SelectStmt, label *lblock) {
|
||||
// First evaluate channel expressions.
|
||||
// TODO(adonovan): fix: evaluate only channel exprs here.
|
||||
for _, clause := range s.Body.List {
|
||||
if comm := clause.(*ast.CommClause).Comm; comm != nil {
|
||||
b.stmt(comm)
|
||||
}
|
||||
}
|
||||
|
||||
done := b.newBlock(KindSelectDone, s)
|
||||
if label != nil {
|
||||
label._break = done
|
||||
}
|
||||
|
||||
var defaultBody *[]ast.Stmt
|
||||
for _, cc := range s.Body.List {
|
||||
clause := cc.(*ast.CommClause)
|
||||
if clause.Comm == nil {
|
||||
defaultBody = &clause.Body
|
||||
continue
|
||||
}
|
||||
body := b.newBlock(KindSelectCaseBody, clause)
|
||||
next := b.newBlock(KindSelectAfterCase, clause)
|
||||
b.ifelse(body, next)
|
||||
b.current = body
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
}
|
||||
switch comm := clause.Comm.(type) {
|
||||
case *ast.ExprStmt: // <-ch
|
||||
// nop
|
||||
case *ast.AssignStmt: // x := <-states[state].Chan
|
||||
b.add(comm.Lhs[0])
|
||||
}
|
||||
b.stmtList(clause.Body)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(done)
|
||||
b.current = next
|
||||
}
|
||||
if defaultBody != nil {
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
}
|
||||
b.stmtList(*defaultBody)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(done)
|
||||
}
|
||||
b.current = done
|
||||
}
|
||||
|
||||
func (b *builder) forStmt(s *ast.ForStmt, label *lblock) {
|
||||
// ...init...
|
||||
// jump loop
|
||||
// loop:
|
||||
// if cond goto body else done
|
||||
// body:
|
||||
// ...body...
|
||||
// jump post
|
||||
// post: (target of continue)
|
||||
// ...post...
|
||||
// jump loop
|
||||
// done: (target of break)
|
||||
if s.Init != nil {
|
||||
b.stmt(s.Init)
|
||||
}
|
||||
body := b.newBlock(KindForBody, s)
|
||||
done := b.newBlock(KindForDone, s) // target of 'break'
|
||||
loop := body // target of back-edge
|
||||
if s.Cond != nil {
|
||||
loop = b.newBlock(KindForLoop, s)
|
||||
}
|
||||
cont := loop // target of 'continue'
|
||||
if s.Post != nil {
|
||||
cont = b.newBlock(KindForPost, s)
|
||||
}
|
||||
if label != nil {
|
||||
label._break = done
|
||||
label._continue = cont
|
||||
}
|
||||
b.jump(loop)
|
||||
b.current = loop
|
||||
if loop != body {
|
||||
b.add(s.Cond)
|
||||
b.ifelse(body, done)
|
||||
b.current = body
|
||||
}
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
_continue: cont,
|
||||
}
|
||||
b.stmt(s.Body)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(cont)
|
||||
|
||||
if s.Post != nil {
|
||||
b.current = cont
|
||||
b.stmt(s.Post)
|
||||
b.jump(loop) // back-edge
|
||||
}
|
||||
b.current = done
|
||||
}
|
||||
|
||||
func (b *builder) rangeStmt(s *ast.RangeStmt, label *lblock) {
|
||||
b.add(s.X)
|
||||
|
||||
if s.Key != nil {
|
||||
b.add(s.Key)
|
||||
}
|
||||
if s.Value != nil {
|
||||
b.add(s.Value)
|
||||
}
|
||||
|
||||
// ...
|
||||
// loop: (target of continue)
|
||||
// if ... goto body else done
|
||||
// body:
|
||||
// ...
|
||||
// jump loop
|
||||
// done: (target of break)
|
||||
|
||||
loop := b.newBlock(KindRangeLoop, s)
|
||||
b.jump(loop)
|
||||
b.current = loop
|
||||
|
||||
body := b.newBlock(KindRangeBody, s)
|
||||
done := b.newBlock(KindRangeDone, s)
|
||||
b.ifelse(body, done)
|
||||
b.current = body
|
||||
|
||||
if label != nil {
|
||||
label._break = done
|
||||
label._continue = loop
|
||||
}
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
_continue: loop,
|
||||
}
|
||||
b.stmt(s.Body)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(loop) // back-edge
|
||||
b.current = done
|
||||
}
|
||||
|
||||
// -------- helpers --------
|
||||
|
||||
// Destinations associated with unlabeled for/switch/select stmts.
|
||||
// We push/pop one of these as we enter/leave each construct and for
|
||||
// each BranchStmt we scan for the innermost target of the right type.
|
||||
type targets struct {
|
||||
tail *targets // rest of stack
|
||||
_break *Block
|
||||
_continue *Block
|
||||
_fallthrough *Block
|
||||
}
|
||||
|
||||
// Destinations associated with a labeled block.
|
||||
// We populate these as labels are encountered in forward gotos or
|
||||
// labeled statements.
|
||||
type lblock struct {
|
||||
_goto *Block
|
||||
_break *Block
|
||||
_continue *Block
|
||||
}
|
||||
|
||||
// labeledBlock returns the branch target associated with the
|
||||
// specified label, creating it if needed.
|
||||
func (b *builder) labeledBlock(label *ast.Ident, stmt *ast.LabeledStmt) *lblock {
|
||||
lb := b.lblocks[label.Name]
|
||||
if lb == nil {
|
||||
lb = &lblock{_goto: b.newBlock(KindLabel, nil)}
|
||||
if b.lblocks == nil {
|
||||
b.lblocks = make(map[string]*lblock)
|
||||
}
|
||||
b.lblocks[label.Name] = lb
|
||||
}
|
||||
// Fill in the label later (in case of forward goto).
|
||||
// Stmt may be set already if labels are duplicated (ill-typed).
|
||||
if stmt != nil && lb._goto.Stmt == nil {
|
||||
lb._goto.Stmt = stmt
|
||||
}
|
||||
return lb
|
||||
}
|
||||
|
||||
// newBlock appends a new unconnected basic block to b.cfg's block
|
||||
// slice and returns it.
|
||||
// It does not automatically become the current block.
|
||||
// comment is an optional string for more readable debugging output.
|
||||
func (b *builder) newBlock(kind BlockKind, stmt ast.Stmt) *Block {
|
||||
block := &Block{
|
||||
Index: int32(len(b.blocks)),
|
||||
Kind: kind,
|
||||
Stmt: stmt,
|
||||
}
|
||||
block.Succs = block.succs2[:0]
|
||||
b.blocks = append(b.blocks, block)
|
||||
return block
|
||||
}
|
||||
|
||||
func (b *builder) add(n ast.Node) {
|
||||
b.current.Nodes = append(b.current.Nodes, n)
|
||||
}
|
||||
|
||||
// jump adds an edge from the current block to the target block,
|
||||
// and sets b.current to nil.
|
||||
func (b *builder) jump(target *Block) {
|
||||
b.current.Succs = append(b.current.Succs, target)
|
||||
b.current = nil
|
||||
}
|
||||
|
||||
// ifelse emits edges from the current block to the t and f blocks,
|
||||
// and sets b.current to nil.
|
||||
func (b *builder) ifelse(t, f *Block) {
|
||||
b.current.Succs = append(b.current.Succs, t, f)
|
||||
b.current = nil
|
||||
}
|
||||
273
vendor/golang.org/x/tools/go/cfg/cfg.go
generated
vendored
Normal file
273
vendor/golang.org/x/tools/go/cfg/cfg.go
generated
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package cfg constructs a simple control-flow graph (CFG) of the
|
||||
// statements and expressions within a single function.
|
||||
//
|
||||
// Use cfg.New to construct the CFG for a function body.
|
||||
//
|
||||
// The blocks of the CFG contain all the function's non-control
|
||||
// statements. The CFG does not contain control statements such as If,
|
||||
// Switch, Select, and Branch, but does contain their subexpressions;
|
||||
// also, each block records the control statement (Block.Stmt) that
|
||||
// gave rise to it and its relationship (Block.Kind) to that statement.
|
||||
//
|
||||
// For example, this source code:
|
||||
//
|
||||
// if x := f(); x != nil {
|
||||
// T()
|
||||
// } else {
|
||||
// F()
|
||||
// }
|
||||
//
|
||||
// produces this CFG:
|
||||
//
|
||||
// 1: x := f() Body
|
||||
// x != nil
|
||||
// succs: 2, 3
|
||||
// 2: T() IfThen
|
||||
// succs: 4
|
||||
// 3: F() IfElse
|
||||
// succs: 4
|
||||
// 4: IfDone
|
||||
//
|
||||
// The CFG does contain Return statements; even implicit returns are
|
||||
// materialized (at the position of the function's closing brace).
|
||||
//
|
||||
// The CFG does not record conditions associated with conditional branch
|
||||
// edges, nor the short-circuit semantics of the && and || operators,
|
||||
// nor abnormal control flow caused by panic. If you need this
|
||||
// information, use golang.org/x/tools/go/ssa instead.
|
||||
package cfg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// A CFG represents the control-flow graph of a single function.
|
||||
//
|
||||
// The entry point is Blocks[0]; there may be multiple return blocks.
|
||||
type CFG struct {
|
||||
Blocks []*Block // block[0] is entry; order otherwise undefined
|
||||
noreturn bool // function body lacks a reachable return statement
|
||||
}
|
||||
|
||||
// NoReturn reports whether the function has no reachable return.
|
||||
func (cfg *CFG) NoReturn() bool { return cfg.noreturn }
|
||||
|
||||
// A Block represents a basic block: a list of statements and
|
||||
// expressions that are always evaluated sequentially.
|
||||
//
|
||||
// A block may have 0-2 successors: zero for a return block or a block
|
||||
// that calls a function such as panic that never returns; one for a
|
||||
// normal (jump) block; and two for a conditional (if) block.
|
||||
//
|
||||
// In a conditional block, the last entry in Nodes is the condition and always
|
||||
// an [ast.Expr], Succs[0] is the successor if the condition is true, and
|
||||
// Succs[1] is the successor if the condition is false.
|
||||
type Block struct {
|
||||
Nodes []ast.Node // statements, expressions, and ValueSpecs
|
||||
Succs []*Block // successor nodes in the graph
|
||||
Index int32 // index within CFG.Blocks
|
||||
Live bool // block is reachable from entry
|
||||
returns bool // block contains return or defer (which may recover and return)
|
||||
Kind BlockKind // block kind
|
||||
Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details)
|
||||
|
||||
succs2 [2]*Block // underlying array for Succs
|
||||
}
|
||||
|
||||
// A BlockKind identifies the purpose of a block.
|
||||
// It also determines the possible types of its Stmt field.
|
||||
type BlockKind uint8
|
||||
|
||||
const (
|
||||
KindInvalid BlockKind = iota // Stmt=nil
|
||||
|
||||
KindUnreachable // unreachable block after {Branch,Return}Stmt / no-return call ExprStmt
|
||||
KindBody // function body BlockStmt
|
||||
KindForBody // body of ForStmt
|
||||
KindForDone // block after ForStmt
|
||||
KindForLoop // head of ForStmt
|
||||
KindForPost // post condition of ForStmt
|
||||
KindIfDone // block after IfStmt
|
||||
KindIfElse // else block of IfStmt
|
||||
KindIfThen // then block of IfStmt
|
||||
KindLabel // labeled block of BranchStmt (Stmt may be nil for dangling label)
|
||||
KindRangeBody // body of RangeStmt
|
||||
KindRangeDone // block after RangeStmt
|
||||
KindRangeLoop // head of RangeStmt
|
||||
KindSelectCaseBody // body of SelectStmt
|
||||
KindSelectDone // block after SelectStmt
|
||||
KindSelectAfterCase // block after a CommClause
|
||||
KindSwitchCaseBody // body of CaseClause
|
||||
KindSwitchDone // block after {Type.}SwitchStmt
|
||||
KindSwitchNextCase // secondary expression of a multi-expression CaseClause
|
||||
)
|
||||
|
||||
func (kind BlockKind) String() string {
|
||||
return [...]string{
|
||||
KindInvalid: "Invalid",
|
||||
KindUnreachable: "Unreachable",
|
||||
KindBody: "Body",
|
||||
KindForBody: "ForBody",
|
||||
KindForDone: "ForDone",
|
||||
KindForLoop: "ForLoop",
|
||||
KindForPost: "ForPost",
|
||||
KindIfDone: "IfDone",
|
||||
KindIfElse: "IfElse",
|
||||
KindIfThen: "IfThen",
|
||||
KindLabel: "Label",
|
||||
KindRangeBody: "RangeBody",
|
||||
KindRangeDone: "RangeDone",
|
||||
KindRangeLoop: "RangeLoop",
|
||||
KindSelectCaseBody: "SelectCaseBody",
|
||||
KindSelectDone: "SelectDone",
|
||||
KindSelectAfterCase: "SelectAfterCase",
|
||||
KindSwitchCaseBody: "SwitchCaseBody",
|
||||
KindSwitchDone: "SwitchDone",
|
||||
KindSwitchNextCase: "SwitchNextCase",
|
||||
}[kind]
|
||||
}
|
||||
|
||||
// New returns a new control-flow graph for the specified function body,
|
||||
// which must be non-nil.
|
||||
//
|
||||
// The CFG builder calls mayReturn to determine whether a given function
|
||||
// call may return. For example, calls to panic, os.Exit, and log.Fatal
|
||||
// do not return, so the builder can remove infeasible graph edges
|
||||
// following such calls. The builder calls mayReturn only for a
|
||||
// CallExpr beneath an ExprStmt.
|
||||
func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG {
|
||||
b := builder{
|
||||
mayReturn: mayReturn,
|
||||
}
|
||||
b.current = b.newBlock(KindBody, body)
|
||||
b.stmt(body)
|
||||
|
||||
// Compute liveness (reachability from entry point),
|
||||
// breadth-first, marking Block.Live flags.
|
||||
q := make([]*Block, 0, len(b.blocks))
|
||||
q = append(q, b.blocks[0]) // entry point
|
||||
for len(q) > 0 {
|
||||
b := q[len(q)-1]
|
||||
q = q[:len(q)-1]
|
||||
|
||||
if !b.Live {
|
||||
b.Live = true
|
||||
q = append(q, b.Succs...)
|
||||
}
|
||||
}
|
||||
|
||||
// Does control fall off the end of the function's body?
|
||||
// Make implicit return explicit.
|
||||
if b.current != nil && b.current.Live {
|
||||
b.current.returns = true
|
||||
b.add(&ast.ReturnStmt{
|
||||
Return: body.End() - 1,
|
||||
})
|
||||
}
|
||||
|
||||
// Is any return (or defer+recover) block reachable?
|
||||
noreturn := true
|
||||
for _, bl := range b.blocks {
|
||||
if bl.Live && bl.returns {
|
||||
noreturn = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &CFG{Blocks: b.blocks, noreturn: noreturn}
|
||||
}
|
||||
|
||||
func (b *Block) String() string {
|
||||
return fmt.Sprintf("block %d (%s)", b.Index, b.comment(nil))
|
||||
}
|
||||
|
||||
func (b *Block) comment(fset *token.FileSet) string {
|
||||
s := b.Kind.String()
|
||||
if fset != nil && b.Stmt != nil {
|
||||
s = fmt.Sprintf("%s@L%d", s, fset.Position(b.Stmt.Pos()).Line)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Return returns the return statement at the end of this block if present, nil
|
||||
// otherwise.
|
||||
//
|
||||
// When control falls off the end of the function, the ReturnStmt is synthetic
|
||||
// and its [ast.Node.End] position may be beyond the end of the file.
|
||||
//
|
||||
// A function that contains no return statement (explicit or implied)
|
||||
// may yet return normally, and may even return a nonzero value. For example:
|
||||
//
|
||||
// func() (res any) {
|
||||
// defer func() { res = recover() }()
|
||||
// panic(123)
|
||||
// }
|
||||
func (b *Block) Return() (ret *ast.ReturnStmt) {
|
||||
if len(b.Nodes) > 0 {
|
||||
ret, _ = b.Nodes[len(b.Nodes)-1].(*ast.ReturnStmt)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Format formats the control-flow graph for ease of debugging.
|
||||
func (g *CFG) Format(fset *token.FileSet) string {
|
||||
var buf bytes.Buffer
|
||||
for _, b := range g.Blocks {
|
||||
fmt.Fprintf(&buf, ".%d: # %s\n", b.Index, b.comment(fset))
|
||||
for _, n := range b.Nodes {
|
||||
fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n))
|
||||
}
|
||||
if len(b.Succs) > 0 {
|
||||
fmt.Fprintf(&buf, "\tsuccs:")
|
||||
for _, succ := range b.Succs {
|
||||
fmt.Fprintf(&buf, " %d", succ.Index)
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Dot returns the control-flow graph in the [Dot graph description language].
|
||||
// Use a command such as 'dot -Tsvg' to render it in a form viewable in a browser.
|
||||
// This method is provided as a debugging aid; the details of the
|
||||
// output are unspecified and may change.
|
||||
//
|
||||
// [Dot graph description language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language)
|
||||
func (g *CFG) Dot(fset *token.FileSet) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("digraph CFG {\n")
|
||||
buf.WriteString(" node [shape=box];\n")
|
||||
for _, b := range g.Blocks {
|
||||
// node label
|
||||
var text bytes.Buffer
|
||||
text.WriteString(b.comment(fset))
|
||||
for _, n := range b.Nodes {
|
||||
fmt.Fprintf(&text, "\n%s", formatNode(fset, n))
|
||||
}
|
||||
|
||||
// node and edges
|
||||
fmt.Fprintf(&buf, " n%d [label=%q];\n", b.Index, &text)
|
||||
for _, succ := range b.Succs {
|
||||
fmt.Fprintf(&buf, " n%d -> n%d;\n", b.Index, succ.Index)
|
||||
}
|
||||
}
|
||||
buf.WriteString("}\n")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func formatNode(fset *token.FileSet, n ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
format.Node(&buf, fset, n)
|
||||
// Indent secondary lines by a tab.
|
||||
return string(bytes.Replace(buf.Bytes(), []byte("\n"), []byte("\n\t"), -1))
|
||||
}
|
||||
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.
|
||||
121
vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY
vendored
Normal file
121
vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
Staticcheck and its related tools make use of third party projects,
|
||||
either by reusing their code, or by statically linking them into
|
||||
resulting binaries. These projects are:
|
||||
|
||||
* The Go Programming Language - https://golang.org/
|
||||
golang.org/x/mod - https://github.com/golang/mod
|
||||
golang.org/x/tools - https://github.com/golang/tools
|
||||
golang.org/x/sys - https://github.com/golang/sys
|
||||
golang.org/x/xerrors - https://github.com/golang/xerrors
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
* github.com/BurntSushi/toml - https://github.com/BurntSushi/toml
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
* gogrep - https://github.com/mvdan/gogrep
|
||||
|
||||
Copyright (c) 2017, Daniel Martí. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
* gosmith - https://github.com/dvyukov/gosmith
|
||||
|
||||
Copyright (c) 2014 Dmitry Vyukov. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* The name of Dmitry Vyukov may be used to endorse or promote
|
||||
products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
161
vendor/honnef.co/go/tools/analysis/callcheck/callcheck.go
vendored
Normal file
161
vendor/honnef.co/go/tools/analysis/callcheck/callcheck.go
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// Package callcheck provides a framework for validating arguments in function calls.
|
||||
package callcheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
)
|
||||
|
||||
type Call struct {
|
||||
Pass *analysis.Pass
|
||||
Instr ir.CallInstruction
|
||||
Args []*Argument
|
||||
|
||||
Parent *ir.Function
|
||||
|
||||
invalids []string
|
||||
}
|
||||
|
||||
func (c *Call) Invalid(msg string) {
|
||||
c.invalids = append(c.invalids, msg)
|
||||
}
|
||||
|
||||
type Argument struct {
|
||||
Value Value
|
||||
invalids []string
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Value ir.Value
|
||||
}
|
||||
|
||||
func (arg *Argument) Invalid(msg string) {
|
||||
arg.invalids = append(arg.invalids, msg)
|
||||
}
|
||||
|
||||
type Check func(call *Call)
|
||||
|
||||
func Analyzer(rules map[string]Check) func(pass *analysis.Pass) (any, error) {
|
||||
return func(pass *analysis.Pass) (any, error) {
|
||||
return checkCalls(pass, rules)
|
||||
}
|
||||
}
|
||||
|
||||
func checkCalls(pass *analysis.Pass, rules map[string]Check) (any, error) {
|
||||
cb := func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
|
||||
obj, ok := callee.Object().(*types.Func)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
r, ok := rules[typeutil.FuncName(obj)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var args []*Argument
|
||||
irargs := site.Common().Args
|
||||
if callee.Signature.Recv() != nil {
|
||||
irargs = irargs[1:]
|
||||
}
|
||||
for _, arg := range irargs {
|
||||
if iarg, ok := arg.(*ir.MakeInterface); ok {
|
||||
arg = iarg.X
|
||||
}
|
||||
args = append(args, &Argument{Value: Value{arg}})
|
||||
}
|
||||
call := &Call{
|
||||
Pass: pass,
|
||||
Instr: site,
|
||||
Args: args,
|
||||
Parent: site.Parent(),
|
||||
}
|
||||
r(call)
|
||||
|
||||
var astcall *ast.CallExpr
|
||||
switch source := site.Source().(type) {
|
||||
case *ast.CallExpr:
|
||||
astcall = source
|
||||
case *ast.DeferStmt:
|
||||
astcall = source.Call
|
||||
case *ast.GoStmt:
|
||||
astcall = source.Call
|
||||
case nil:
|
||||
// TODO(dh): I am not sure this can actually happen. If it
|
||||
// can't, we should remove this case, and also stop
|
||||
// checking for astcall == nil in the code that follows.
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled case %T", source))
|
||||
}
|
||||
|
||||
for idx, arg := range call.Args {
|
||||
for _, e := range arg.invalids {
|
||||
if astcall != nil {
|
||||
if idx < len(astcall.Args) {
|
||||
report.Report(pass, astcall.Args[idx], e)
|
||||
} else {
|
||||
// this is an instance of fn1(fn2()) where fn2
|
||||
// returns multiple values. Report the error
|
||||
// at the next-best position that we have, the
|
||||
// first argument. An example of a check that
|
||||
// triggers this is checkEncodingBinaryRules.
|
||||
report.Report(pass, astcall.Args[0], e)
|
||||
}
|
||||
} else {
|
||||
report.Report(pass, site, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, e := range call.invalids {
|
||||
report.Report(pass, call.Instr, e)
|
||||
}
|
||||
}
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
eachCall(fn, cb)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) {
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if site, ok := instr.(ir.CallInstruction); ok {
|
||||
if g := site.Common().StaticCallee(); g != nil {
|
||||
cb(fn, site, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExtractConstExpectKind(v Value, kind constant.Kind) *ir.Const {
|
||||
k := extractConst(v.Value)
|
||||
if k == nil || k.Value == nil || k.Value.Kind() != kind {
|
||||
return nil
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func ExtractConst(v Value) *ir.Const {
|
||||
return extractConst(v.Value)
|
||||
}
|
||||
|
||||
func extractConst(v ir.Value) *ir.Const {
|
||||
v = irutil.Flatten(v)
|
||||
switch v := v.(type) {
|
||||
case *ir.Const:
|
||||
return v
|
||||
case *ir.MakeInterface:
|
||||
return extractConst(v.X)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
589
vendor/honnef.co/go/tools/analysis/code/code.go
vendored
Normal file
589
vendor/honnef.co/go/tools/analysis/code/code.go
vendored
Normal file
@@ -0,0 +1,589 @@
|
||||
// Package code answers structural and type questions about Go code.
|
||||
package code
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build/constraint"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"go/version"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/facts/purity"
|
||||
"honnef.co/go/tools/analysis/facts/tokenfile"
|
||||
"honnef.co/go/tools/go/ast/astutil"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type Positioner interface {
|
||||
Pos() token.Pos
|
||||
}
|
||||
|
||||
func IsOfStringConvertibleByteSlice(pass *analysis.Pass, expr ast.Expr) bool {
|
||||
typ, ok := pass.TypesInfo.TypeOf(expr).Underlying().(*types.Slice)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
elem := types.Unalias(typ.Elem())
|
||||
if version.Compare(LanguageVersion(pass, expr), "go1.18") >= 0 {
|
||||
// Before Go 1.18, one could not directly convert from []T (where 'type T byte')
|
||||
// to string. See also https://github.com/golang/go/issues/23536.
|
||||
elem = elem.Underlying()
|
||||
}
|
||||
return types.Identical(elem, types.Typ[types.Byte])
|
||||
}
|
||||
|
||||
func IsOfPointerToTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
|
||||
ptr, ok := types.Unalias(pass.TypesInfo.TypeOf(expr)).(*types.Pointer)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return typeutil.IsTypeWithName(ptr.Elem(), name)
|
||||
}
|
||||
|
||||
func IsOfTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
|
||||
return typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(expr), name)
|
||||
}
|
||||
|
||||
func IsInTest(pass *analysis.Pass, node Positioner) bool {
|
||||
// FIXME(dh): this doesn't work for global variables with
|
||||
// initializers
|
||||
f := pass.Fset.File(node.Pos())
|
||||
return f != nil && strings.HasSuffix(f.Name(), "_test.go")
|
||||
}
|
||||
|
||||
// IsMain reports whether the package being processed is a package
|
||||
// main.
|
||||
func IsMain(pass *analysis.Pass) bool {
|
||||
return pass.Pkg.Name() == "main"
|
||||
}
|
||||
|
||||
// IsMainLike reports whether the package being processed is a
|
||||
// main-like package. A main-like package is a package that is
|
||||
// package main, or that is intended to be used by a tool framework
|
||||
// such as cobra to implement a command.
|
||||
//
|
||||
// Note that this function errs on the side of false positives; it may
|
||||
// return true for packages that aren't main-like. IsMainLike is
|
||||
// intended for analyses that wish to suppress diagnostics for
|
||||
// main-like packages to avoid false positives.
|
||||
func IsMainLike(pass *analysis.Pass) bool {
|
||||
if pass.Pkg.Name() == "main" {
|
||||
return true
|
||||
}
|
||||
for _, imp := range pass.Pkg.Imports() {
|
||||
if imp.Path() == "github.com/spf13/cobra" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
|
||||
info := pass.TypesInfo
|
||||
sel := info.Selections[expr]
|
||||
if sel == nil {
|
||||
if x, ok := expr.X.(*ast.Ident); ok {
|
||||
pkg, ok := 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))
|
||||
}
|
||||
if v, ok := sel.Obj().(*types.Var); ok && v.IsField() {
|
||||
return fmt.Sprintf("(%s).%s", typeutil.DereferenceR(sel.Recv()), sel.Obj().Name())
|
||||
} else {
|
||||
return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
|
||||
}
|
||||
}
|
||||
|
||||
func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
|
||||
return pass.TypesInfo.Types[expr].IsNil()
|
||||
}
|
||||
|
||||
func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
|
||||
val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
|
||||
return constant.BoolVal(val)
|
||||
}
|
||||
|
||||
func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool {
|
||||
// We explicitly don't support typed bools because more often than
|
||||
// not, custom bool types are used as binary enums and the explicit
|
||||
// comparison is desired. We err on the side of false negatives and
|
||||
// treat aliases like other custom types.
|
||||
|
||||
ident, ok := expr.(*ast.Ident)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
obj := pass.TypesInfo.ObjectOf(ident)
|
||||
c, ok := obj.(*types.Const)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
basic, ok := c.Type().(*types.Basic)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
|
||||
tv := pass.TypesInfo.Types[expr]
|
||||
if tv.Value == nil {
|
||||
return 0, false
|
||||
}
|
||||
if tv.Value.Kind() != constant.Int {
|
||||
return 0, false
|
||||
}
|
||||
return constant.Int64Val(tv.Value)
|
||||
}
|
||||
|
||||
func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
|
||||
val := pass.TypesInfo.Types[expr].Value
|
||||
if val == nil {
|
||||
return "", false
|
||||
}
|
||||
if val.Kind() != constant.String {
|
||||
return "", false
|
||||
}
|
||||
return constant.StringVal(val), true
|
||||
}
|
||||
|
||||
func CallName(pass *analysis.Pass, call *ast.CallExpr) string {
|
||||
// See the comment in typeutil.FuncName for why this doesn't require special handling
|
||||
// of aliases.
|
||||
|
||||
fun := astutil.Unparen(call.Fun)
|
||||
|
||||
// Instantiating a function cannot return another generic function, so doing this once is enough
|
||||
switch idx := fun.(type) {
|
||||
case *ast.IndexExpr:
|
||||
fun = idx.X
|
||||
case *ast.IndexListExpr:
|
||||
fun = idx.X
|
||||
}
|
||||
|
||||
// (foo)[T] is not a valid instantiation, so no need to unparen again.
|
||||
|
||||
switch fun := fun.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return typeutil.FuncName(fn)
|
||||
case *ast.Ident:
|
||||
obj := pass.TypesInfo.ObjectOf(fun)
|
||||
switch obj := obj.(type) {
|
||||
case *types.Func:
|
||||
return typeutil.FuncName(obj)
|
||||
case *types.Builtin:
|
||||
return obj.Name()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool {
|
||||
// See the comment in typeutil.FuncName for why this doesn't require special handling
|
||||
// of aliases.
|
||||
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return CallName(pass, call) == name
|
||||
}
|
||||
|
||||
func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool {
|
||||
// See the comment in typeutil.FuncName for why this doesn't require special handling
|
||||
// of aliases.
|
||||
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
q := CallName(pass, call)
|
||||
return slices.Contains(names, q)
|
||||
}
|
||||
|
||||
func File(pass *analysis.Pass, node Positioner) *ast.File {
|
||||
m := pass.ResultOf[tokenfile.Analyzer].(map[*token.File]*ast.File)
|
||||
return m[pass.Fset.File(node.Pos())]
|
||||
}
|
||||
|
||||
// BuildConstraints returns the build constraints for file f. It considers both //go:build lines as well as
|
||||
// GOOS and GOARCH in file names.
|
||||
func BuildConstraints(pass *analysis.Pass, f *ast.File) (constraint.Expr, bool) {
|
||||
var expr constraint.Expr
|
||||
for _, cmt := range f.Comments {
|
||||
if len(cmt.List) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, el := range cmt.List {
|
||||
if el.Pos() > f.Package {
|
||||
break
|
||||
}
|
||||
if line := el.Text; strings.HasPrefix(line, "//go:build") {
|
||||
var err error
|
||||
expr, err = constraint.Parse(line)
|
||||
if err != nil {
|
||||
expr = nil
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name := pass.Fset.PositionFor(f.Pos(), false).Filename
|
||||
oexpr := constraintsFromName(name)
|
||||
if oexpr != nil {
|
||||
if expr == nil {
|
||||
expr = oexpr
|
||||
} else {
|
||||
expr = &constraint.AndExpr{X: expr, Y: oexpr}
|
||||
}
|
||||
}
|
||||
|
||||
return expr, expr != nil
|
||||
}
|
||||
|
||||
func constraintsFromName(name string) constraint.Expr {
|
||||
name = filepath.Base(name)
|
||||
name = strings.TrimSuffix(name, ".go")
|
||||
name = strings.TrimSuffix(name, "_test")
|
||||
var goos, goarch string
|
||||
switch strings.Count(name, "_") {
|
||||
case 0:
|
||||
// No GOOS or GOARCH in the file name.
|
||||
case 1:
|
||||
_, c, _ := strings.Cut(name, "_")
|
||||
if _, ok := knowledge.KnownGOOS[c]; ok {
|
||||
goos = c
|
||||
} else if _, ok := knowledge.KnownGOARCH[c]; ok {
|
||||
goarch = c
|
||||
}
|
||||
default:
|
||||
n := strings.LastIndex(name, "_")
|
||||
if _, ok := knowledge.KnownGOOS[name[n+1:]]; ok {
|
||||
// The file name is *_stuff_GOOS.go
|
||||
goos = name[n+1:]
|
||||
} else if _, ok := knowledge.KnownGOARCH[name[n+1:]]; ok {
|
||||
// The file name is *_GOOS_GOARCH.go or *_stuff_GOARCH.go
|
||||
goarch = name[n+1:]
|
||||
_, c, _ := strings.Cut(name[:n], "_")
|
||||
if _, ok := knowledge.KnownGOOS[c]; ok {
|
||||
// The file name is *_GOOS_GOARCH.go
|
||||
goos = c
|
||||
}
|
||||
} else {
|
||||
// The file name could also be something like foo_windows_nonsense.go — and because nonsense
|
||||
// isn't a known GOARCH, "windows" won't be interpreted as a GOOS, either.
|
||||
}
|
||||
}
|
||||
|
||||
var expr constraint.Expr
|
||||
if goos != "" {
|
||||
expr = &constraint.TagExpr{Tag: goos}
|
||||
}
|
||||
if goarch != "" {
|
||||
if expr == nil {
|
||||
expr = &constraint.TagExpr{Tag: goarch}
|
||||
} else {
|
||||
expr = &constraint.AndExpr{X: expr, Y: &constraint.TagExpr{Tag: goarch}}
|
||||
}
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
// IsGenerated reports whether pos is in a generated file. It ignores
|
||||
// //line directives.
|
||||
func IsGenerated(pass *analysis.Pass, pos token.Pos) bool {
|
||||
_, ok := Generator(pass, pos)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Generator returns the generator that generated the file containing
|
||||
// pos. It ignores //line directives.
|
||||
func Generator(pass *analysis.Pass, pos token.Pos) (generated.Generator, bool) {
|
||||
file := pass.Fset.PositionFor(pos, false).Filename
|
||||
m := pass.ResultOf[generated.Analyzer].(map[string]generated.Generator)
|
||||
g, ok := m[file]
|
||||
return g, ok
|
||||
}
|
||||
|
||||
// MayHaveSideEffects reports whether expr may have side effects. If
|
||||
// the purity argument is nil, this function implements a purely
|
||||
// syntactic check, meaning that any function call may have side
|
||||
// effects, regardless of the called function's body. Otherwise,
|
||||
// purity will be consulted to determine the purity of function calls.
|
||||
func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity purity.Result) bool {
|
||||
switch expr := expr.(type) {
|
||||
case *ast.BadExpr:
|
||||
return true
|
||||
case *ast.Ellipsis:
|
||||
return MayHaveSideEffects(pass, expr.Elt, purity)
|
||||
case *ast.FuncLit:
|
||||
// the literal itself cannot have side effects, only calling it
|
||||
// might, which is handled by CallExpr.
|
||||
return false
|
||||
case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
|
||||
// types cannot have side effects
|
||||
return false
|
||||
case *ast.BasicLit:
|
||||
return false
|
||||
case *ast.BinaryExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity)
|
||||
case *ast.CallExpr:
|
||||
if purity == nil {
|
||||
return true
|
||||
}
|
||||
switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) {
|
||||
case *types.Func:
|
||||
if _, ok := purity[obj]; !ok {
|
||||
return true
|
||||
}
|
||||
case *types.Builtin:
|
||||
switch obj.Name() {
|
||||
case "len", "cap":
|
||||
default:
|
||||
return true
|
||||
}
|
||||
default:
|
||||
return true
|
||||
}
|
||||
for _, arg := range expr.Args {
|
||||
if MayHaveSideEffects(pass, arg, purity) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case *ast.CompositeLit:
|
||||
if MayHaveSideEffects(pass, expr.Type, purity) {
|
||||
return true
|
||||
}
|
||||
for _, elt := range expr.Elts {
|
||||
if MayHaveSideEffects(pass, elt, purity) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case *ast.Ident:
|
||||
return false
|
||||
case *ast.IndexExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity)
|
||||
case *ast.IndexListExpr:
|
||||
// In theory, none of the checks are necessary, as IndexListExpr only involves types. But there is no harm in
|
||||
// being safe.
|
||||
if MayHaveSideEffects(pass, expr.X, purity) {
|
||||
return true
|
||||
}
|
||||
for _, idx := range expr.Indices {
|
||||
if MayHaveSideEffects(pass, idx, purity) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case *ast.KeyValueExpr:
|
||||
return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity)
|
||||
case *ast.SelectorExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity)
|
||||
case *ast.SliceExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity) ||
|
||||
MayHaveSideEffects(pass, expr.Low, purity) ||
|
||||
MayHaveSideEffects(pass, expr.High, purity) ||
|
||||
MayHaveSideEffects(pass, expr.Max, purity)
|
||||
case *ast.StarExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity)
|
||||
case *ast.TypeAssertExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity)
|
||||
case *ast.UnaryExpr:
|
||||
if MayHaveSideEffects(pass, expr.X, purity) {
|
||||
return true
|
||||
}
|
||||
return expr.Op == token.ARROW || expr.Op == token.AND
|
||||
case *ast.ParenExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity)
|
||||
case nil:
|
||||
return false
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled type %T", expr))
|
||||
}
|
||||
}
|
||||
|
||||
// LanguageVersion returns the version of the Go language that node has access to. This
|
||||
// might differ from the version of the Go standard library.
|
||||
func LanguageVersion(pass *analysis.Pass, node Positioner) string {
|
||||
// As of Go 1.21, two places can specify the minimum Go version:
|
||||
// - 'go' directives in go.mod and go.work files
|
||||
// - individual files by using '//go:build'
|
||||
//
|
||||
// Individual files can upgrade to a higher version than the module version. Individual files
|
||||
// can also downgrade to a lower version, but only if the module version is at least Go 1.21.
|
||||
//
|
||||
// The restriction on downgrading doesn't matter to us. All language changes before Go 1.22 will
|
||||
// not type-check on versions that are too old, and thus never reach our analyzes. In practice,
|
||||
// such ineffective downgrading will always be useless, as the compiler will not restrict the
|
||||
// language features used, and doesn't ever rely on minimum versions to restrict the use of the
|
||||
// standard library. However, for us, both choices (respecting or ignoring ineffective
|
||||
// downgrading) have equal complexity, but only respecting it has a non-zero chance of reducing
|
||||
// noisy positives.
|
||||
//
|
||||
// The minimum Go versions are exposed via go/ast.File.GoVersion and go/types.Package.GoVersion.
|
||||
// ast.File's version is populated by the parser, whereas types.Package's version is populated
|
||||
// from the Go version specified in the types.Config, which is set by our package loader, based
|
||||
// on the module information provided by go/packages, via 'go list -json'.
|
||||
//
|
||||
// As of Go 1.21, standard library packages do not present themselves as modules, and thus do
|
||||
// not have a version set on their types.Package. In this case, we fall back to the version
|
||||
// provided by our '-go' flag. In most cases, '-go' defaults to 'module', which falls back to
|
||||
// the Go version that Staticcheck was built with when no module information exists. In the
|
||||
// future, the standard library will hopefully be a proper module (see
|
||||
// https://github.com/golang/go/issues/61174#issuecomment-1622471317). In that case, the version
|
||||
// of standard library packages will match that of the used Go version. At that point,
|
||||
// Staticcheck will refuse to work with Go versions that are too new, to avoid misinterpreting
|
||||
// code due to language changes.
|
||||
//
|
||||
// We also lack module information when building in GOPATH mode. In this case, the implied
|
||||
// language version is at most Go 1.21, as per https://github.com/golang/go/issues/60915. We
|
||||
// don't handle this yet, and it will not matter until Go 1.22.
|
||||
//
|
||||
// It is not clear how per-file downgrading behaves in GOPATH mode. On the one hand, no module
|
||||
// version at all is provided, which should preclude per-file downgrading. On the other hand,
|
||||
// https://github.com/golang/go/issues/60915 suggests that the language version is at most 1.21
|
||||
// in GOPATH mode, which would allow per-file downgrading. Again it doesn't affect us, as all
|
||||
// relevant language changes before Go 1.22 will lead to type-checking failures and never reach
|
||||
// us.
|
||||
//
|
||||
// Per-file upgrading is permitted in GOPATH mode.
|
||||
|
||||
// If the file has its own Go version, we will return that. Otherwise, we default to
|
||||
// the type checker's GoVersion, which is populated from either the Go module, or from
|
||||
// our '-go' flag.
|
||||
return pass.TypesInfo.FileVersions[File(pass, node)]
|
||||
}
|
||||
|
||||
// StdlibVersion returns the version of the Go standard library that node can expect to
|
||||
// have access to. This might differ from the language version for versions of Go older
|
||||
// than 1.21.
|
||||
func StdlibVersion(pass *analysis.Pass, node Positioner) string {
|
||||
// The Go version as specified in go.mod or via the '-go' flag
|
||||
n := pass.Pkg.GoVersion()
|
||||
|
||||
f := File(pass, node)
|
||||
if f == nil {
|
||||
panic(fmt.Sprintf("no file found for node with position %s", pass.Fset.PositionFor(node.Pos(), false)))
|
||||
}
|
||||
|
||||
if nf := f.GoVersion; nf != "" {
|
||||
if version.Compare(n, "go1.21") == -1 {
|
||||
// Before Go 1.21, the Go version set in go.mod specified the maximum language
|
||||
// version available to the module. It wasn't uncommon to set the version to
|
||||
// Go 1.20 but restrict usage of 1.20 functionality (both language and stdlib)
|
||||
// to files tagged for 1.20, and supporting a lower version overall. As such,
|
||||
// a file tagged lower than the module version couldn't expect to have access
|
||||
// to the standard library of the version set in go.mod.
|
||||
//
|
||||
// At the same time, a file tagged higher than the module version, while not
|
||||
// able to use newer language features, would still have been able to use a
|
||||
// newer standard library.
|
||||
//
|
||||
// While Go 1.21's behavior has been backported to 1.19.11 and 1.20.6, users'
|
||||
// expectations have not.
|
||||
return nf
|
||||
} else {
|
||||
// Go 1.21 and newer refuse to build modules that depend on versions newer
|
||||
// than the used version of the Go toolchain. This means that in a 1.22 module
|
||||
// with a file tagged as 1.17, the file can expect to have access to 1.22's
|
||||
// standard library (but not to 1.22 language features). A file tagged with a
|
||||
// version higher than the minimum version has access to the newer standard
|
||||
// library (and language features.)
|
||||
//
|
||||
// Do note that strictly speaking we're conflating the Go version and the
|
||||
// module version in our check. Nothing is stopping a user from using Go 1.17
|
||||
// (which didn't implement the new rules for versions in go.mod) to build a Go
|
||||
// 1.22 module, in which case a file tagged with go1.17 will not have access to the 1.22
|
||||
// standard library. However, we believe that if a module requires 1.21 or
|
||||
// newer, then the author clearly expects the new behavior, and doesn't care
|
||||
// for the old one. Otherwise they would've specified an older version.
|
||||
//
|
||||
// In other words, the module version also specifies what it itself actually means, with
|
||||
// >=1.21 being a minimum version for the toolchain, and <1.21 being a maximum version for
|
||||
// the language.
|
||||
|
||||
if version.Compare(nf, n) == 1 {
|
||||
return nf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
var integerLiteralQ = pattern.MustParse(`(IntegerLiteral tv)`)
|
||||
|
||||
func IntegerLiteral(pass *analysis.Pass, node ast.Node) (types.TypeAndValue, bool) {
|
||||
m, ok := Match(pass, integerLiteralQ, node)
|
||||
if !ok {
|
||||
return types.TypeAndValue{}, false
|
||||
}
|
||||
return m.State["tv"].(types.TypeAndValue), true
|
||||
}
|
||||
|
||||
func IsIntegerLiteral(pass *analysis.Pass, node ast.Node, value constant.Value) bool {
|
||||
tv, ok := IntegerLiteral(pass, node)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return constant.Compare(tv.Value, token.EQL, value)
|
||||
}
|
||||
|
||||
// IsMethod reports whether expr is a method call of a named method with signature meth.
|
||||
// If name is empty, it is not checked.
|
||||
// For now, method expressions (Type.Method(recv, ..)) are not considered method calls.
|
||||
func IsMethod(pass *analysis.Pass, expr *ast.SelectorExpr, name string, meth *types.Signature) bool {
|
||||
if name != "" && expr.Sel.Name != name {
|
||||
return false
|
||||
}
|
||||
sel, ok := pass.TypesInfo.Selections[expr]
|
||||
if !ok || sel.Kind() != types.MethodVal {
|
||||
return false
|
||||
}
|
||||
return types.Identical(sel.Type(), meth)
|
||||
}
|
||||
|
||||
func RefersTo(pass *analysis.Pass, expr ast.Expr, ident types.Object) bool {
|
||||
found := false
|
||||
fn := func(node ast.Node) bool {
|
||||
ident2, ok := node.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if ident == pass.TypesInfo.ObjectOf(ident2) {
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
ast.Inspect(expr, fn)
|
||||
return found
|
||||
}
|
||||
153
vendor/honnef.co/go/tools/analysis/code/visit.go
vendored
Normal file
153
vendor/honnef.co/go/tools/analysis/code/visit.go
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
package code
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/types"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
typeindexanalyzer "honnef.co/go/tools/internal/analysisinternal/typeindex"
|
||||
"honnef.co/go/tools/internal/typesinternal/typeindex"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var RequiredAnalyzers = []*analysis.Analyzer{inspect.Analyzer, typeindexanalyzer.Analyzer}
|
||||
|
||||
func Cursor(pass *analysis.Pass) inspector.Cursor {
|
||||
return pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root()
|
||||
}
|
||||
|
||||
func Preorder(pass *analysis.Pass, fn func(ast.Node), types ...ast.Node) {
|
||||
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder(types, fn)
|
||||
}
|
||||
|
||||
func PreorderStack(pass *analysis.Pass, fn func(ast.Node, []ast.Node), types ...ast.Node) {
|
||||
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).WithStack(types, func(n ast.Node, push bool, stack []ast.Node) (proceed bool) {
|
||||
if push {
|
||||
fn(n, stack)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func Matches(pass *analysis.Pass, qs ...pattern.Pattern) iter.Seq2[ast.Node, *pattern.Matcher] {
|
||||
return func(yield func(ast.Node, *pattern.Matcher) bool) {
|
||||
for _, q := range qs {
|
||||
if !CouldMatchAny(pass, q) {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(q.RootCallSymbols) != 0 {
|
||||
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
|
||||
for _, isym := range q.RootCallSymbols {
|
||||
var obj types.Object
|
||||
if isym.Type == "" {
|
||||
obj = index.Object(isym.Path, isym.Ident)
|
||||
} else {
|
||||
obj = index.Selection(isym.Path, isym.Type, isym.Ident)
|
||||
}
|
||||
for c := range index.Calls(obj) {
|
||||
node := c.Node()
|
||||
if m, ok := Match(pass, q, node); ok {
|
||||
if !yield(node, m) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ins := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
fn := func(node ast.Node, push bool) bool {
|
||||
if !push {
|
||||
return true
|
||||
}
|
||||
|
||||
if m, ok := Match(pass, q, node); ok {
|
||||
return yield(node, m)
|
||||
}
|
||||
return true
|
||||
}
|
||||
ins.Nodes(q.EntryNodes, fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Match(pass *analysis.Pass, q pattern.Pattern, node ast.Node) (*pattern.Matcher, bool) {
|
||||
// Note that we ignore q.Relevant – callers of Match usually use
|
||||
// AST inspectors that already filter on nodes we're interested
|
||||
// in.
|
||||
m := &pattern.Matcher{TypesInfo: pass.TypesInfo}
|
||||
ok := m.Match(q, node)
|
||||
return m, ok
|
||||
}
|
||||
|
||||
func CouldMatchAny(pass *analysis.Pass, qs ...pattern.Pattern) bool {
|
||||
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
|
||||
var do func(node pattern.Node) bool
|
||||
do = func(node pattern.Node) bool {
|
||||
switch node := node.(type) {
|
||||
case pattern.Any:
|
||||
return true
|
||||
case pattern.Or:
|
||||
return slices.ContainsFunc(node.Nodes, do)
|
||||
case pattern.And:
|
||||
for _, child := range node.Nodes {
|
||||
if !do(child) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case pattern.IndexSymbol:
|
||||
if node.Type == "" {
|
||||
return index.Object(node.Path, node.Ident) != nil
|
||||
} else {
|
||||
return index.Selection(node.Path, node.Type, node.Ident) != nil
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unexpected type %T", node))
|
||||
}
|
||||
}
|
||||
|
||||
for _, q := range qs {
|
||||
if do(q.SymbolsPattern) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func MatchAndEdit(pass *analysis.Pass, before, after pattern.Pattern, node ast.Node) (*pattern.Matcher, []analysis.TextEdit, bool) {
|
||||
m, ok := Match(pass, before, node)
|
||||
if !ok {
|
||||
return m, nil, false
|
||||
}
|
||||
r := pattern.NodeToAST(after.Root, m.State)
|
||||
buf := &bytes.Buffer{}
|
||||
format.Node(buf, pass.Fset, r)
|
||||
edit := []analysis.TextEdit{{
|
||||
Pos: node.Pos(),
|
||||
End: node.End(),
|
||||
NewText: buf.Bytes(),
|
||||
}}
|
||||
return m, edit, true
|
||||
}
|
||||
|
||||
func EditMatch(pass *analysis.Pass, node ast.Node, m *pattern.Matcher, after pattern.Pattern) []analysis.TextEdit {
|
||||
r := pattern.NodeToAST(after.Root, m.State)
|
||||
buf := &bytes.Buffer{}
|
||||
format.Node(buf, pass.Fset, r)
|
||||
edit := []analysis.TextEdit{{
|
||||
Pos: node.Pos(),
|
||||
End: node.End(),
|
||||
NewText: buf.Bytes(),
|
||||
}}
|
||||
return edit
|
||||
}
|
||||
83
vendor/honnef.co/go/tools/analysis/edit/edit.go
vendored
Normal file
83
vendor/honnef.co/go/tools/analysis/edit/edit.go
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
// Package edit contains helpers for creating suggested fixes.
|
||||
package edit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/pattern"
|
||||
)
|
||||
|
||||
// Ranger describes values that have a start and end position.
|
||||
// In most cases these are either ast.Node or manually constructed ranges.
|
||||
type Ranger interface {
|
||||
Pos() token.Pos
|
||||
End() token.Pos
|
||||
}
|
||||
|
||||
// Range implements the Ranger interface.
|
||||
type Range [2]token.Pos
|
||||
|
||||
func (r Range) Pos() token.Pos { return r[0] }
|
||||
func (r Range) End() token.Pos { return r[1] }
|
||||
|
||||
// ReplaceWithString replaces a range with a string.
|
||||
func ReplaceWithString(old Ranger, new string) analysis.TextEdit {
|
||||
return analysis.TextEdit{
|
||||
Pos: old.Pos(),
|
||||
End: old.End(),
|
||||
NewText: []byte(new),
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceWithNode replaces a range with an AST node.
|
||||
func ReplaceWithNode(fset *token.FileSet, old Ranger, new ast.Node) analysis.TextEdit {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := format.Node(buf, fset, new); err != nil {
|
||||
panic("internal error: " + err.Error())
|
||||
}
|
||||
return analysis.TextEdit{
|
||||
Pos: old.Pos(),
|
||||
End: old.End(),
|
||||
NewText: buf.Bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceWithPattern replaces a range with the result of executing a pattern.
|
||||
func ReplaceWithPattern(fset *token.FileSet, old Ranger, new pattern.Pattern, state pattern.State) analysis.TextEdit {
|
||||
r := pattern.NodeToAST(new.Root, state)
|
||||
buf := &bytes.Buffer{}
|
||||
format.Node(buf, fset, r)
|
||||
return analysis.TextEdit{
|
||||
Pos: old.Pos(),
|
||||
End: old.End(),
|
||||
NewText: buf.Bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
// Delete deletes a range of code.
|
||||
func Delete(old Ranger) analysis.TextEdit {
|
||||
return analysis.TextEdit{
|
||||
Pos: old.Pos(),
|
||||
End: old.End(),
|
||||
NewText: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func Fix(msg string, edits ...analysis.TextEdit) analysis.SuggestedFix {
|
||||
return analysis.SuggestedFix{
|
||||
Message: msg,
|
||||
TextEdits: edits,
|
||||
}
|
||||
}
|
||||
|
||||
// Selector creates a new selector expression.
|
||||
func Selector(x, sel string) *ast.SelectorExpr {
|
||||
return &ast.SelectorExpr{
|
||||
X: &ast.Ident{Name: x},
|
||||
Sel: &ast.Ident{Name: sel},
|
||||
}
|
||||
}
|
||||
154
vendor/honnef.co/go/tools/analysis/facts/deprecated/deprecated.go
vendored
Normal file
154
vendor/honnef.co/go/tools/analysis/facts/deprecated/deprecated.go
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
package deprecated
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type IsDeprecated struct{ Msg string }
|
||||
|
||||
func (*IsDeprecated) AFact() {}
|
||||
func (d *IsDeprecated) String() string { return "Deprecated: " + d.Msg }
|
||||
|
||||
type Result struct {
|
||||
Objects map[types.Object]*IsDeprecated
|
||||
Packages map[*types.Package]*IsDeprecated
|
||||
}
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "fact_deprecated",
|
||||
Doc: "Mark deprecated objects",
|
||||
Run: deprecated,
|
||||
FactTypes: []analysis.Fact{(*IsDeprecated)(nil)},
|
||||
ResultType: reflect.TypeFor[Result](),
|
||||
}
|
||||
|
||||
func deprecated(pass *analysis.Pass) (any, error) {
|
||||
var names []*ast.Ident
|
||||
|
||||
extractDeprecatedMessage := func(docs []*ast.CommentGroup) string {
|
||||
for _, doc := range docs {
|
||||
if doc == nil {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitSeq(doc.Text(), "\n\n")
|
||||
for part := range parts {
|
||||
if !strings.HasPrefix(part, "Deprecated: ") {
|
||||
continue
|
||||
}
|
||||
alt := part[len("Deprecated: "):]
|
||||
alt = strings.Replace(alt, "\n", " ", -1)
|
||||
return alt
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
doDocs := func(names []*ast.Ident, docs []*ast.CommentGroup) {
|
||||
alt := extractDeprecatedMessage(docs)
|
||||
if alt == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
obj := pass.TypesInfo.ObjectOf(name)
|
||||
pass.ExportObjectFact(obj, &IsDeprecated{alt})
|
||||
}
|
||||
}
|
||||
|
||||
var docs []*ast.CommentGroup
|
||||
for _, f := range pass.Files {
|
||||
docs = append(docs, f.Doc)
|
||||
}
|
||||
if alt := extractDeprecatedMessage(docs); alt != "" {
|
||||
// Don't mark package syscall as deprecated, even though
|
||||
// it is. A lot of people still use it for simple
|
||||
// constants like SIGKILL, and I am not comfortable
|
||||
// telling them to use x/sys for that.
|
||||
if pass.Pkg.Path() != "syscall" {
|
||||
pass.ExportPackageFact(&IsDeprecated{alt})
|
||||
}
|
||||
}
|
||||
|
||||
docs = docs[:0]
|
||||
for _, f := range pass.Files {
|
||||
fn := func(node ast.Node) bool {
|
||||
if node == nil {
|
||||
return true
|
||||
}
|
||||
var ret bool
|
||||
switch node := node.(type) {
|
||||
case *ast.GenDecl:
|
||||
switch node.Tok {
|
||||
case token.TYPE, token.CONST, token.VAR:
|
||||
docs = append(docs, node.Doc)
|
||||
for i := range node.Specs {
|
||||
switch n := node.Specs[i].(type) {
|
||||
case *ast.ValueSpec:
|
||||
names = append(names, n.Names...)
|
||||
case *ast.TypeSpec:
|
||||
names = append(names, n.Name)
|
||||
}
|
||||
}
|
||||
ret = true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
docs = append(docs, node.Doc)
|
||||
names = []*ast.Ident{node.Name}
|
||||
ret = false
|
||||
case *ast.TypeSpec:
|
||||
docs = append(docs, node.Doc)
|
||||
names = []*ast.Ident{node.Name}
|
||||
ret = true
|
||||
case *ast.ValueSpec:
|
||||
docs = append(docs, node.Doc)
|
||||
names = node.Names
|
||||
ret = false
|
||||
case *ast.File:
|
||||
return true
|
||||
case *ast.StructType:
|
||||
for _, field := range node.Fields.List {
|
||||
doDocs(field.Names, []*ast.CommentGroup{field.Doc})
|
||||
}
|
||||
return false
|
||||
case *ast.InterfaceType:
|
||||
for _, field := range node.Methods.List {
|
||||
doDocs(field.Names, []*ast.CommentGroup{field.Doc})
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if len(names) == 0 || len(docs) == 0 {
|
||||
return ret
|
||||
}
|
||||
doDocs(names, docs)
|
||||
|
||||
docs = docs[:0]
|
||||
names = nil
|
||||
return ret
|
||||
}
|
||||
ast.Inspect(f, fn)
|
||||
}
|
||||
|
||||
out := Result{
|
||||
Objects: map[types.Object]*IsDeprecated{},
|
||||
Packages: map[*types.Package]*IsDeprecated{},
|
||||
}
|
||||
|
||||
for _, fact := range pass.AllObjectFacts() {
|
||||
out.Objects[fact.Object] = fact.Fact.(*IsDeprecated)
|
||||
}
|
||||
for _, fact := range pass.AllPackageFacts() {
|
||||
out.Packages[fact.Package] = fact.Fact.(*IsDeprecated)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
20
vendor/honnef.co/go/tools/analysis/facts/directives/directives.go
vendored
Normal file
20
vendor/honnef.co/go/tools/analysis/facts/directives/directives.go
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package directives
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
)
|
||||
|
||||
func directives(pass *analysis.Pass) (any, error) {
|
||||
return lint.ParseDirectives(pass.Files, pass.Fset), nil
|
||||
}
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "directives",
|
||||
Doc: "extracts linter directives",
|
||||
Run: directives,
|
||||
RunDespiteErrors: true,
|
||||
ResultType: reflect.TypeFor[[]lint.Directive](),
|
||||
}
|
||||
97
vendor/honnef.co/go/tools/analysis/facts/generated/generated.go
vendored
Normal file
97
vendor/honnef.co/go/tools/analysis/facts/generated/generated.go
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
package generated
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type Generator int
|
||||
|
||||
// A list of known generators we can detect
|
||||
const (
|
||||
Unknown Generator = iota
|
||||
Goyacc
|
||||
Cgo
|
||||
Stringer
|
||||
ProtocGenGo
|
||||
)
|
||||
|
||||
var (
|
||||
// used by cgo before Go 1.11
|
||||
oldCgo = []byte("// Created by cgo - DO NOT EDIT")
|
||||
prefix = []byte("// Code generated ")
|
||||
suffix = []byte(" DO NOT EDIT.")
|
||||
nl = []byte("\n")
|
||||
crnl = []byte("\r\n")
|
||||
)
|
||||
|
||||
func isGenerated(path string) (Generator, bool) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
defer f.Close()
|
||||
br := bufio.NewReader(f)
|
||||
for {
|
||||
s, err := br.ReadBytes('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
return 0, false
|
||||
}
|
||||
s = bytes.TrimSuffix(s, crnl)
|
||||
s = bytes.TrimSuffix(s, nl)
|
||||
if bytes.HasPrefix(s, prefix) && bytes.HasSuffix(s, suffix) {
|
||||
if len(s)-len(suffix) < len(prefix) {
|
||||
return Unknown, true
|
||||
}
|
||||
|
||||
text := string(s[len(prefix) : len(s)-len(suffix)])
|
||||
switch text {
|
||||
case "by goyacc.":
|
||||
return Goyacc, true
|
||||
case "by cmd/cgo;":
|
||||
return Cgo, true
|
||||
case "by protoc-gen-go.":
|
||||
return ProtocGenGo, true
|
||||
}
|
||||
if strings.HasPrefix(text, `by "stringer `) {
|
||||
return Stringer, true
|
||||
}
|
||||
if strings.HasPrefix(text, `by goyacc `) {
|
||||
return Goyacc, true
|
||||
}
|
||||
|
||||
return Unknown, true
|
||||
}
|
||||
if bytes.Equal(s, oldCgo) {
|
||||
return Cgo, true
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "isgenerated",
|
||||
Doc: "annotate file names that have been code generated",
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
m := map[string]Generator{}
|
||||
for _, f := range pass.Files {
|
||||
path := pass.Fset.PositionFor(f.Pos(), false).Filename
|
||||
g, ok := isGenerated(path)
|
||||
if ok {
|
||||
m[path] = g
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
},
|
||||
RunDespiteErrors: true,
|
||||
ResultType: reflect.TypeFor[map[string]Generator](),
|
||||
}
|
||||
255
vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go
vendored
Normal file
255
vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go
vendored
Normal file
@@ -0,0 +1,255 @@
|
||||
package nilness
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
// neverReturnsNilFact denotes that a function's return value will never
|
||||
// be nil (typed or untyped). The analysis errs on the side of false
|
||||
// negatives.
|
||||
type neverReturnsNilFact struct {
|
||||
Rets []neverNilness
|
||||
}
|
||||
|
||||
func (*neverReturnsNilFact) AFact() {}
|
||||
func (fact *neverReturnsNilFact) String() string {
|
||||
return fmt.Sprintf("never returns nil: %v", fact.Rets)
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
m map[*types.Func][]neverNilness
|
||||
}
|
||||
|
||||
var Analysis = &analysis.Analyzer{
|
||||
Name: "nilness",
|
||||
Doc: "Annotates return values that will never be nil (typed or untyped)",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
FactTypes: []analysis.Fact{(*neverReturnsNilFact)(nil)},
|
||||
ResultType: reflect.TypeFor[*Result](),
|
||||
}
|
||||
|
||||
// MayReturnNil reports whether the ret's return value of fn might be
|
||||
// a typed or untyped nil value. The value of ret is zero-based. When
|
||||
// globalOnly is true, the only possible nil values are global
|
||||
// variables.
|
||||
//
|
||||
// The analysis has false positives: MayReturnNil can incorrectly
|
||||
// report true, but never incorrectly reports false.
|
||||
func (r *Result) MayReturnNil(fn *types.Func, ret int) (yes bool, globalOnly bool) {
|
||||
if !typeutil.IsPointerLike(fn.Type().(*types.Signature).Results().At(ret).Type()) {
|
||||
return false, false
|
||||
}
|
||||
if len(r.m[fn]) == 0 {
|
||||
return true, false
|
||||
}
|
||||
|
||||
v := r.m[fn][ret]
|
||||
return v != neverNil, v == onlyGlobal
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
seen := map[*ir.Function]struct{}{}
|
||||
out := &Result{
|
||||
m: map[*types.Func][]neverNilness{},
|
||||
}
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
impl(pass, fn, seen)
|
||||
}
|
||||
|
||||
for _, fact := range pass.AllObjectFacts() {
|
||||
out.m[fact.Object.(*types.Func)] = fact.Fact.(*neverReturnsNilFact).Rets
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type neverNilness uint8
|
||||
|
||||
const (
|
||||
neverNil neverNilness = 1
|
||||
onlyGlobal neverNilness = 2
|
||||
nilly neverNilness = 3
|
||||
)
|
||||
|
||||
func (n neverNilness) String() string {
|
||||
switch n {
|
||||
case neverNil:
|
||||
return "never"
|
||||
case onlyGlobal:
|
||||
return "global"
|
||||
case nilly:
|
||||
return "nil"
|
||||
default:
|
||||
return "BUG"
|
||||
}
|
||||
}
|
||||
|
||||
func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) []neverNilness {
|
||||
if fn.Object() == nil {
|
||||
// TODO(dh): support closures
|
||||
return nil
|
||||
}
|
||||
if fact := new(neverReturnsNilFact); pass.ImportObjectFact(fn.Object(), fact) {
|
||||
return fact.Rets
|
||||
}
|
||||
if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg {
|
||||
return nil
|
||||
}
|
||||
if fn.Blocks == nil {
|
||||
return nil
|
||||
}
|
||||
if _, ok := seenFns[fn]; ok {
|
||||
// break recursion
|
||||
return nil
|
||||
}
|
||||
|
||||
seenFns[fn] = struct{}{}
|
||||
|
||||
seen := map[ir.Value]struct{}{}
|
||||
|
||||
var mightReturnNil func(v ir.Value) neverNilness
|
||||
mightReturnNil = func(v ir.Value) neverNilness {
|
||||
if _, ok := seen[v]; ok {
|
||||
// break cycle
|
||||
return nilly
|
||||
}
|
||||
if !typeutil.IsPointerLike(v.Type()) {
|
||||
return neverNil
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
switch v := v.(type) {
|
||||
case *ir.MakeInterface:
|
||||
return mightReturnNil(v.X)
|
||||
case *ir.Convert:
|
||||
return mightReturnNil(v.X)
|
||||
case *ir.SliceToArrayPointer:
|
||||
if typeutil.CoreType(v.Type()).(*types.Pointer).Elem().Underlying().(*types.Array).Len() == 0 {
|
||||
return mightReturnNil(v.X)
|
||||
} else {
|
||||
// converting a slice to an array pointer of length > 0 panics if the slice is nil
|
||||
return neverNil
|
||||
}
|
||||
case *ir.Slice:
|
||||
return mightReturnNil(v.X)
|
||||
case *ir.Phi:
|
||||
ret := neverNil
|
||||
for _, e := range v.Edges {
|
||||
if n := mightReturnNil(e); n > ret {
|
||||
ret = n
|
||||
}
|
||||
}
|
||||
return ret
|
||||
case *ir.Extract:
|
||||
switch d := v.Tuple.(type) {
|
||||
case *ir.Call:
|
||||
if callee := d.Call.StaticCallee(); callee != nil {
|
||||
ret := impl(pass, callee, seenFns)
|
||||
if len(ret) == 0 {
|
||||
return nilly
|
||||
}
|
||||
return ret[v.Index]
|
||||
} else {
|
||||
return nilly
|
||||
}
|
||||
case *ir.TypeAssert, *ir.Next, *ir.Select, *ir.MapLookup, *ir.TypeSwitch, *ir.Recv, *ir.Sigma:
|
||||
// we don't need to look at the Extract's index
|
||||
// because we've already checked its type.
|
||||
return nilly
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled type %T", d))
|
||||
}
|
||||
case *ir.Call:
|
||||
if callee := v.Call.StaticCallee(); callee != nil {
|
||||
ret := impl(pass, callee, seenFns)
|
||||
if len(ret) == 0 {
|
||||
return nilly
|
||||
}
|
||||
return ret[0]
|
||||
} else {
|
||||
return nilly
|
||||
}
|
||||
case *ir.BinOp, *ir.UnOp, *ir.Alloc, *ir.FieldAddr, *ir.IndexAddr, *ir.Global, *ir.MakeSlice, *ir.MakeClosure, *ir.Function, *ir.MakeMap, *ir.MakeChan:
|
||||
return neverNil
|
||||
case *ir.Sigma:
|
||||
iff, ok := v.From.Control().(*ir.If)
|
||||
if !ok {
|
||||
return nilly
|
||||
}
|
||||
binop, ok := iff.Cond.(*ir.BinOp)
|
||||
if !ok {
|
||||
return nilly
|
||||
}
|
||||
isNil := func(v ir.Value) bool {
|
||||
k, ok := v.(*ir.Const)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return k.Value == nil
|
||||
}
|
||||
if binop.X == v.X && isNil(binop.Y) || binop.Y == v.X && isNil(binop.X) {
|
||||
op := binop.Op
|
||||
if v.From.Succs[0] != v.Block() {
|
||||
// we're in the false branch, negate op
|
||||
switch op {
|
||||
case token.EQL:
|
||||
op = token.NEQ
|
||||
case token.NEQ:
|
||||
op = token.EQL
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled token %v", op))
|
||||
}
|
||||
}
|
||||
switch op {
|
||||
case token.EQL:
|
||||
return nilly
|
||||
case token.NEQ:
|
||||
return neverNil
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled token %v", op))
|
||||
}
|
||||
}
|
||||
return nilly
|
||||
case *ir.ChangeType:
|
||||
return mightReturnNil(v.X)
|
||||
case *ir.MultiConvert:
|
||||
return mightReturnNil(v.X)
|
||||
case *ir.Load:
|
||||
if _, ok := v.X.(*ir.Global); ok {
|
||||
return onlyGlobal
|
||||
}
|
||||
return nilly
|
||||
case *ir.AggregateConst:
|
||||
return neverNil
|
||||
case *ir.TypeAssert, *ir.ChangeInterface, *ir.Field, *ir.Const, *ir.GenericConst, *ir.Index, *ir.MapLookup, *ir.Parameter, *ir.Recv, *ir.TypeSwitch:
|
||||
return nilly
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled type %T", v))
|
||||
}
|
||||
}
|
||||
ret := fn.Exit.Control().(*ir.Return)
|
||||
out := make([]neverNilness, len(ret.Results))
|
||||
export := false
|
||||
for i, v := range ret.Results {
|
||||
// OPT(dh): couldn't we check the result type's pointer-likeness early, and skip
|
||||
// processing the return value altogether?
|
||||
v := mightReturnNil(v)
|
||||
out[i] = v
|
||||
if v != nilly && typeutil.IsPointerLike(fn.Signature.Results().At(i).Type()) {
|
||||
export = true
|
||||
}
|
||||
}
|
||||
if export {
|
||||
pass.ExportObjectFact(fn.Object(), &neverReturnsNilFact{out})
|
||||
}
|
||||
return out
|
||||
}
|
||||
264
vendor/honnef.co/go/tools/analysis/facts/purity/purity.go
vendored
Normal file
264
vendor/honnef.co/go/tools/analysis/facts/purity/purity.go
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
package purity
|
||||
|
||||
// TODO(dh): we should split this into two facts, one tracking actual purity, and one tracking side-effects. A function
|
||||
// that returns a heap allocation isn't pure, but it may be free of side effects.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"reflect"
|
||||
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type IsPure struct{}
|
||||
|
||||
func (*IsPure) AFact() {}
|
||||
func (d *IsPure) String() string { return "is pure" }
|
||||
|
||||
type Result map[*types.Func]*IsPure
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "fact_purity",
|
||||
Doc: "Mark pure functions",
|
||||
Run: purity,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
FactTypes: []analysis.Fact{(*IsPure)(nil)},
|
||||
ResultType: reflect.TypeFor[Result](),
|
||||
}
|
||||
|
||||
var pureStdlib = map[string]struct{}{
|
||||
"errors.New": {},
|
||||
"fmt.Errorf": {},
|
||||
"fmt.Sprintf": {},
|
||||
"fmt.Sprint": {},
|
||||
"sort.Reverse": {},
|
||||
"strings.Map": {},
|
||||
"strings.Repeat": {},
|
||||
"strings.Replace": {},
|
||||
"strings.Title": {},
|
||||
"strings.ToLower": {},
|
||||
"strings.ToLowerSpecial": {},
|
||||
"strings.ToTitle": {},
|
||||
"strings.ToTitleSpecial": {},
|
||||
"strings.ToUpper": {},
|
||||
"strings.ToUpperSpecial": {},
|
||||
"strings.Trim": {},
|
||||
"strings.TrimFunc": {},
|
||||
"strings.TrimLeft": {},
|
||||
"strings.TrimLeftFunc": {},
|
||||
"strings.TrimPrefix": {},
|
||||
"strings.TrimRight": {},
|
||||
"strings.TrimRightFunc": {},
|
||||
"strings.TrimSpace": {},
|
||||
"strings.TrimSuffix": {},
|
||||
"(*net/http.Request).WithContext": {},
|
||||
"time.Now": {},
|
||||
"time.Parse": {},
|
||||
"time.ParseInLocation": {},
|
||||
"time.Unix": {},
|
||||
"time.UnixMicro": {},
|
||||
"time.UnixMilli": {},
|
||||
"(time.Time).Add": {},
|
||||
"(time.Time).AddDate": {},
|
||||
"(time.Time).After": {},
|
||||
"(time.Time).Before": {},
|
||||
"(time.Time).Clock": {},
|
||||
"(time.Time).Compare": {},
|
||||
"(time.Time).Date": {},
|
||||
"(time.Time).Day": {},
|
||||
"(time.Time).Equal": {},
|
||||
"(time.Time).Format": {},
|
||||
"(time.Time).GoString": {},
|
||||
"(time.Time).GobEncode": {},
|
||||
"(time.Time).Hour": {},
|
||||
"(time.Time).ISOWeek": {},
|
||||
"(time.Time).In": {},
|
||||
"(time.Time).IsDST": {},
|
||||
"(time.Time).IsZero": {},
|
||||
"(time.Time).Local": {},
|
||||
"(time.Time).Location": {},
|
||||
"(time.Time).MarshalBinary": {},
|
||||
"(time.Time).MarshalJSON": {},
|
||||
"(time.Time).MarshalText": {},
|
||||
"(time.Time).Minute": {},
|
||||
"(time.Time).Month": {},
|
||||
"(time.Time).Nanosecond": {},
|
||||
"(time.Time).Round": {},
|
||||
"(time.Time).Second": {},
|
||||
"(time.Time).String": {},
|
||||
"(time.Time).Sub": {},
|
||||
"(time.Time).Truncate": {},
|
||||
"(time.Time).UTC": {},
|
||||
"(time.Time).Unix": {},
|
||||
"(time.Time).UnixMicro": {},
|
||||
"(time.Time).UnixMilli": {},
|
||||
"(time.Time).UnixNano": {},
|
||||
"(time.Time).Weekday": {},
|
||||
"(time.Time).Year": {},
|
||||
"(time.Time).YearDay": {},
|
||||
"(time.Time).Zone": {},
|
||||
"(time.Time).ZoneBounds": {},
|
||||
}
|
||||
|
||||
func purity(pass *analysis.Pass) (any, error) {
|
||||
seen := map[*ir.Function]struct{}{}
|
||||
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
|
||||
var check func(fn *ir.Function) (ret bool)
|
||||
check = func(fn *ir.Function) (ret bool) {
|
||||
if fn.Object() == nil {
|
||||
// TODO(dh): support closures
|
||||
return false
|
||||
}
|
||||
if pass.ImportObjectFact(fn.Object(), new(IsPure)) {
|
||||
return true
|
||||
}
|
||||
if fn.Pkg != irpkg {
|
||||
// Function is in another package but wasn't marked as
|
||||
// pure, ergo it isn't pure
|
||||
return false
|
||||
}
|
||||
// Break recursion
|
||||
if _, ok := seen[fn]; ok {
|
||||
return false
|
||||
}
|
||||
|
||||
seen[fn] = struct{}{}
|
||||
defer func() {
|
||||
if ret {
|
||||
pass.ExportObjectFact(fn.Object(), &IsPure{})
|
||||
}
|
||||
}()
|
||||
|
||||
if irutil.IsStub(fn) {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if fn.Signature.Results().Len() == 0 {
|
||||
// A function with no return values is empty or is doing some
|
||||
// work we cannot see (for example because of build tags);
|
||||
// don't consider it pure.
|
||||
return false
|
||||
}
|
||||
|
||||
var isBasic func(typ types.Type) bool
|
||||
isBasic = func(typ types.Type) bool {
|
||||
switch u := typ.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
return true
|
||||
case *types.Struct:
|
||||
for field := range u.Fields() {
|
||||
if !isBasic(field.Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, param := range fn.Params {
|
||||
// TODO(dh): this may not be strictly correct. pure code can, to an extent, operate on non-basic types.
|
||||
if !isBasic(param.Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Don't consider external functions pure.
|
||||
if fn.Blocks == nil {
|
||||
return false
|
||||
}
|
||||
checkCall := func(common *ir.CallCommon) bool {
|
||||
if common.IsInvoke() {
|
||||
return false
|
||||
}
|
||||
builtin, ok := common.Value.(*ir.Builtin)
|
||||
if !ok {
|
||||
if common.StaticCallee() != fn {
|
||||
if common.StaticCallee() == nil {
|
||||
return false
|
||||
}
|
||||
if !check(common.StaticCallee()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch builtin.Name() {
|
||||
case "len", "cap":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var isStackAddr func(ir.Value) bool
|
||||
isStackAddr = func(v ir.Value) bool {
|
||||
switch v := v.(type) {
|
||||
case *ir.Alloc:
|
||||
return !v.Heap
|
||||
case *ir.FieldAddr:
|
||||
return isStackAddr(v.X)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, b := range fn.Blocks {
|
||||
for _, ins := range b.Instrs {
|
||||
switch ins := ins.(type) {
|
||||
case *ir.Call:
|
||||
if !checkCall(ins.Common()) {
|
||||
return false
|
||||
}
|
||||
case *ir.Defer:
|
||||
if !checkCall(&ins.Call) {
|
||||
return false
|
||||
}
|
||||
case *ir.Select:
|
||||
return false
|
||||
case *ir.Send:
|
||||
return false
|
||||
case *ir.Go:
|
||||
return false
|
||||
case *ir.Panic:
|
||||
return false
|
||||
case *ir.Store:
|
||||
if !isStackAddr(ins.Addr) {
|
||||
return false
|
||||
}
|
||||
case *ir.FieldAddr:
|
||||
if !isStackAddr(ins.X) {
|
||||
return false
|
||||
}
|
||||
case *ir.Alloc:
|
||||
// TODO(dh): make use of proper escape analysis
|
||||
if ins.Heap {
|
||||
return false
|
||||
}
|
||||
case *ir.Load:
|
||||
if !isStackAddr(ins.X) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
check(fn)
|
||||
}
|
||||
|
||||
out := Result{}
|
||||
for _, fact := range pass.AllObjectFacts() {
|
||||
out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
24
vendor/honnef.co/go/tools/analysis/facts/tokenfile/token.go
vendored
Normal file
24
vendor/honnef.co/go/tools/analysis/facts/tokenfile/token.go
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package tokenfile
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "tokenfileanalyzer",
|
||||
Doc: "creates a mapping of *token.File to *ast.File",
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
m := map[*token.File]*ast.File{}
|
||||
for _, af := range pass.Files {
|
||||
tf := pass.Fset.File(af.Pos())
|
||||
m[tf] = af
|
||||
}
|
||||
return m, nil
|
||||
},
|
||||
RunDespiteErrors: true,
|
||||
ResultType: reflect.TypeFor[map[*token.File]*ast.File](),
|
||||
}
|
||||
253
vendor/honnef.co/go/tools/analysis/facts/typedness/typedness.go
vendored
Normal file
253
vendor/honnef.co/go/tools/analysis/facts/typedness/typedness.go
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
package typedness
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/exp/typeparams"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
// alwaysTypedFact denotes that a function's return value will never
|
||||
// be untyped nil. The analysis errs on the side of false negatives.
|
||||
type alwaysTypedFact struct {
|
||||
Rets uint8
|
||||
}
|
||||
|
||||
func (*alwaysTypedFact) AFact() {}
|
||||
func (fact *alwaysTypedFact) String() string {
|
||||
return fmt.Sprintf("always typed: %08b", fact.Rets)
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
m map[*types.Func]uint8
|
||||
}
|
||||
|
||||
var Analysis = &analysis.Analyzer{
|
||||
Name: "typedness",
|
||||
Doc: "Annotates return values that are always typed values",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
FactTypes: []analysis.Fact{(*alwaysTypedFact)(nil)},
|
||||
ResultType: reflect.TypeFor[*Result](),
|
||||
}
|
||||
|
||||
// MustReturnTyped reports whether the ret's return value of fn must
|
||||
// be a typed value, i.e. an interface value containing a concrete
|
||||
// type or trivially a concrete type. The value of ret is zero-based.
|
||||
//
|
||||
// The analysis has false negatives: MustReturnTyped may incorrectly
|
||||
// report false, but never incorrectly reports true.
|
||||
func (r *Result) MustReturnTyped(fn *types.Func, ret int) bool {
|
||||
if _, ok := fn.Type().(*types.Signature).Results().At(ret).Type().Underlying().(*types.Interface); !ok {
|
||||
return true
|
||||
}
|
||||
return (r.m[fn] & (1 << ret)) != 0
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
seen := map[*ir.Function]struct{}{}
|
||||
out := &Result{
|
||||
m: map[*types.Func]uint8{},
|
||||
}
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
impl(pass, fn, seen)
|
||||
}
|
||||
|
||||
for _, fact := range pass.AllObjectFacts() {
|
||||
out.m[fact.Object.(*types.Func)] = fact.Fact.(*alwaysTypedFact).Rets
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) (out uint8) {
|
||||
if fn.Signature.Results().Len() > 8 {
|
||||
return 0
|
||||
}
|
||||
if fn.Object() == nil {
|
||||
// TODO(dh): support closures
|
||||
return 0
|
||||
}
|
||||
if fact := new(alwaysTypedFact); pass.ImportObjectFact(fn.Object(), fact) {
|
||||
return fact.Rets
|
||||
}
|
||||
if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg {
|
||||
return 0
|
||||
}
|
||||
if fn.Blocks == nil {
|
||||
return 0
|
||||
}
|
||||
if irutil.IsStub(fn) {
|
||||
return 0
|
||||
}
|
||||
if _, ok := seenFns[fn]; ok {
|
||||
// break recursion
|
||||
return 0
|
||||
}
|
||||
|
||||
seenFns[fn] = struct{}{}
|
||||
defer func() {
|
||||
for i := 0; i < fn.Signature.Results().Len(); i++ {
|
||||
if _, ok := fn.Signature.Results().At(i).Type().Underlying().(*types.Interface); !ok {
|
||||
// we don't need facts to know that non-interface
|
||||
// types can't be untyped nil. zeroing out those bits
|
||||
// may result in all bits being zero, in which case we
|
||||
// don't have to save any fact.
|
||||
out &= ^(1 << i)
|
||||
}
|
||||
}
|
||||
if out > 0 {
|
||||
pass.ExportObjectFact(fn.Object(), &alwaysTypedFact{out})
|
||||
}
|
||||
}()
|
||||
|
||||
isUntypedNil := func(v ir.Value) bool {
|
||||
k, ok := v.(*ir.Const)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := k.Type().Underlying().(*types.Interface); !ok {
|
||||
return false
|
||||
}
|
||||
return k.Value == nil
|
||||
}
|
||||
|
||||
var do func(v ir.Value, seen map[ir.Value]struct{}) bool
|
||||
do = func(v ir.Value, seen map[ir.Value]struct{}) bool {
|
||||
if _, ok := seen[v]; ok {
|
||||
// break cycle
|
||||
return false
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
switch v := v.(type) {
|
||||
case *ir.Const:
|
||||
// can't be a typed nil, because then we'd be returning the
|
||||
// result of MakeInterface.
|
||||
return false
|
||||
case *ir.ChangeInterface:
|
||||
return do(v.X, seen)
|
||||
case *ir.Extract:
|
||||
call, ok := v.Tuple.(*ir.Call)
|
||||
if !ok {
|
||||
// We only care about extracts of function results. For
|
||||
// everything else (e.g. channel receives and map
|
||||
// lookups), we can either not deduce any information, or
|
||||
// will see a MakeInterface.
|
||||
return false
|
||||
}
|
||||
if callee := call.Call.StaticCallee(); callee != nil {
|
||||
return impl(pass, callee, seenFns)&(1<<v.Index) != 0
|
||||
} else {
|
||||
// we don't know what function we're calling. no need
|
||||
// to look at the signature, though. if it weren't an
|
||||
// interface, we'd be seeing a MakeInterface
|
||||
// instruction.
|
||||
return false
|
||||
}
|
||||
case *ir.Call:
|
||||
if callee := v.Call.StaticCallee(); callee != nil {
|
||||
return impl(pass, callee, seenFns)&1 != 0
|
||||
} else {
|
||||
// we don't know what function we're calling. no need
|
||||
// to look at the signature, though. if it weren't an
|
||||
// interface, we'd be seeing a MakeInterface
|
||||
// instruction.
|
||||
return false
|
||||
}
|
||||
case *ir.Sigma:
|
||||
iff, ok := v.From.Control().(*ir.If)
|
||||
if !ok {
|
||||
// give up
|
||||
return false
|
||||
}
|
||||
|
||||
binop, ok := iff.Cond.(*ir.BinOp)
|
||||
if !ok {
|
||||
// give up
|
||||
return false
|
||||
}
|
||||
|
||||
if (binop.X == v.X && isUntypedNil(binop.Y)) || (isUntypedNil(binop.X) && binop.Y == v.X) {
|
||||
op := binop.Op
|
||||
if v.From.Succs[0] != v.Block() {
|
||||
// we're in the false branch, negate op
|
||||
switch op {
|
||||
case token.EQL:
|
||||
op = token.NEQ
|
||||
case token.NEQ:
|
||||
op = token.EQL
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled token %v", op))
|
||||
}
|
||||
}
|
||||
|
||||
switch op {
|
||||
case token.EQL:
|
||||
// returned value equals untyped nil
|
||||
return false
|
||||
case token.NEQ:
|
||||
// returned value does not equal untyped nil
|
||||
return true
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled token %v", op))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(dh): handle comparison with typed nil
|
||||
|
||||
// give up
|
||||
return false
|
||||
case *ir.Phi:
|
||||
for _, pv := range v.Edges {
|
||||
if !do(pv, seen) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *ir.MakeInterface:
|
||||
terms, err := typeparams.NormalTerms(v.X.Type())
|
||||
if len(terms) == 0 || err != nil {
|
||||
// Type is a type parameter with no type terms (or we couldn't determine the terms). Such a type
|
||||
// _can_ be nil when put in an interface value.
|
||||
//
|
||||
// There is no instruction that can create a guaranteed non-nil instance of a type parameter without
|
||||
// type constraints, so we return false right away, without checking v.X's typedness.
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case *ir.TypeAssert:
|
||||
// type assertions fail for untyped nils. Either we have a
|
||||
// single lhs and the type assertion succeeds or panics,
|
||||
// or we have two lhs and we'll return Extract instead.
|
||||
return true
|
||||
case *ir.ChangeType:
|
||||
// we'll only see interface->interface conversions, which
|
||||
// don't tell us anything about the nilness.
|
||||
return false
|
||||
case *ir.MapLookup, *ir.Index, *ir.Recv, *ir.Parameter, *ir.Load, *ir.Field:
|
||||
// All other instructions that tell us nothing about the
|
||||
// typedness of interface values.
|
||||
return false
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled type %T", v))
|
||||
}
|
||||
}
|
||||
|
||||
ret := fn.Exit.Control().(*ir.Return)
|
||||
for i, v := range ret.Results {
|
||||
typ := fn.Signature.Results().At(i).Type()
|
||||
if _, ok := typ.Underlying().(*types.Interface); ok && !typeparams.IsTypeParam(typ) {
|
||||
if do(v, map[ir.Value]struct{}{}) {
|
||||
out |= 1 << i
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
221
vendor/honnef.co/go/tools/analysis/lint/lint.go
vendored
Normal file
221
vendor/honnef.co/go/tools/analysis/lint/lint.go
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
// Package lint provides abstractions on top of go/analysis.
|
||||
// These abstractions add extra information to analyzes, such as structured documentation and severities.
|
||||
package lint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/analysis/facts/tokenfile"
|
||||
)
|
||||
|
||||
// Analyzer wraps a go/analysis.Analyzer and provides structured documentation.
|
||||
type Analyzer struct {
|
||||
// The analyzer's documentation. Unlike go/analysis.Analyzer.Doc,
|
||||
// this field is structured, providing access to severity, options
|
||||
// etc.
|
||||
Doc *RawDocumentation
|
||||
Analyzer *analysis.Analyzer
|
||||
}
|
||||
|
||||
func InitializeAnalyzer(a *Analyzer) *Analyzer {
|
||||
a.Analyzer.Doc = a.Doc.Compile().String()
|
||||
a.Analyzer.URL = "https://staticcheck.dev/docs/checks/#" + a.Analyzer.Name
|
||||
a.Analyzer.Requires = append(a.Analyzer.Requires, tokenfile.Analyzer)
|
||||
return a
|
||||
}
|
||||
|
||||
// Severity describes the severity of diagnostics reported by an analyzer.
|
||||
type Severity int
|
||||
|
||||
const (
|
||||
SeverityNone Severity = iota
|
||||
SeverityError
|
||||
SeverityDeprecated
|
||||
SeverityWarning
|
||||
SeverityInfo
|
||||
SeverityHint
|
||||
)
|
||||
|
||||
// MergeStrategy sets how merge mode should behave for diagnostics of an analyzer.
|
||||
type MergeStrategy int
|
||||
|
||||
const (
|
||||
MergeIfAny MergeStrategy = iota
|
||||
MergeIfAll
|
||||
)
|
||||
|
||||
type RawDocumentation struct {
|
||||
Title string
|
||||
Text string
|
||||
Before string
|
||||
After string
|
||||
Since string
|
||||
NonDefault bool
|
||||
Options []string
|
||||
Severity Severity
|
||||
MergeIf MergeStrategy
|
||||
}
|
||||
|
||||
type Documentation struct {
|
||||
Title string
|
||||
Text string
|
||||
|
||||
TitleMarkdown string
|
||||
TextMarkdown string
|
||||
|
||||
Before string
|
||||
After string
|
||||
Since string
|
||||
NonDefault bool
|
||||
Options []string
|
||||
Severity Severity
|
||||
MergeIf MergeStrategy
|
||||
}
|
||||
|
||||
func (doc RawDocumentation) Compile() *Documentation {
|
||||
return &Documentation{
|
||||
Title: strings.TrimSpace(stripMarkdown(doc.Title)),
|
||||
Text: strings.TrimSpace(stripMarkdown(doc.Text)),
|
||||
|
||||
TitleMarkdown: strings.TrimSpace(toMarkdown(doc.Title)),
|
||||
TextMarkdown: strings.TrimSpace(toMarkdown(doc.Text)),
|
||||
|
||||
Before: strings.TrimSpace(doc.Before),
|
||||
After: strings.TrimSpace(doc.After),
|
||||
Since: doc.Since,
|
||||
NonDefault: doc.NonDefault,
|
||||
Options: doc.Options,
|
||||
Severity: doc.Severity,
|
||||
MergeIf: doc.MergeIf,
|
||||
}
|
||||
}
|
||||
|
||||
func toMarkdown(s string) string {
|
||||
return strings.NewReplacer(`\'`, "`", `\"`, "`").Replace(s)
|
||||
}
|
||||
|
||||
func stripMarkdown(s string) string {
|
||||
return strings.NewReplacer(`\'`, "", `\"`, "'").Replace(s)
|
||||
}
|
||||
|
||||
func (doc *Documentation) Format(metadata bool) string {
|
||||
return doc.format(false, metadata)
|
||||
}
|
||||
|
||||
func (doc *Documentation) FormatMarkdown(metadata bool) string {
|
||||
return doc.format(true, metadata)
|
||||
}
|
||||
|
||||
func (doc *Documentation) format(markdown bool, metadata bool) string {
|
||||
b := &strings.Builder{}
|
||||
if markdown {
|
||||
fmt.Fprintf(b, "%s\n\n", doc.TitleMarkdown)
|
||||
if doc.Text != "" {
|
||||
fmt.Fprintf(b, "%s\n\n", doc.TextMarkdown)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(b, "%s\n\n", doc.Title)
|
||||
if doc.Text != "" {
|
||||
fmt.Fprintf(b, "%s\n\n", doc.Text)
|
||||
}
|
||||
}
|
||||
|
||||
if doc.Before != "" {
|
||||
fmt.Fprintln(b, "Before:")
|
||||
fmt.Fprintln(b, "")
|
||||
for line := range strings.SplitSeq(doc.Before, "\n") {
|
||||
fmt.Fprint(b, " ", line, "\n")
|
||||
}
|
||||
fmt.Fprintln(b, "")
|
||||
fmt.Fprintln(b, "After:")
|
||||
fmt.Fprintln(b, "")
|
||||
for line := range strings.SplitSeq(doc.After, "\n") {
|
||||
fmt.Fprint(b, " ", line, "\n")
|
||||
}
|
||||
fmt.Fprintln(b, "")
|
||||
}
|
||||
|
||||
if metadata {
|
||||
fmt.Fprint(b, "Available since\n ")
|
||||
if doc.Since == "" {
|
||||
fmt.Fprint(b, "unreleased")
|
||||
} else {
|
||||
fmt.Fprintf(b, "%s", doc.Since)
|
||||
}
|
||||
if doc.NonDefault {
|
||||
fmt.Fprint(b, ", non-default")
|
||||
}
|
||||
fmt.Fprint(b, "\n")
|
||||
if len(doc.Options) > 0 {
|
||||
fmt.Fprintf(b, "\nOptions\n")
|
||||
for _, opt := range doc.Options {
|
||||
fmt.Fprintf(b, " %s", opt)
|
||||
}
|
||||
fmt.Fprint(b, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (doc *Documentation) String() string {
|
||||
return doc.Format(true)
|
||||
}
|
||||
|
||||
// ExhaustiveTypeSwitch panics when called. It can be used to ensure
|
||||
// that type switches are exhaustive.
|
||||
func ExhaustiveTypeSwitch(v any) {
|
||||
panic(fmt.Sprintf("internal error: unhandled case %T", v))
|
||||
}
|
||||
|
||||
// A directive is a comment of the form '//lint:<command>
|
||||
// [arguments...]'. It represents instructions to the static analysis
|
||||
// tool.
|
||||
type Directive struct {
|
||||
Command string
|
||||
Arguments []string
|
||||
Directive *ast.Comment
|
||||
Node ast.Node
|
||||
}
|
||||
|
||||
func parseDirective(s string) (cmd string, args []string) {
|
||||
if !strings.HasPrefix(s, "//lint:") {
|
||||
return "", nil
|
||||
}
|
||||
s = strings.TrimPrefix(s, "//lint:")
|
||||
fields := strings.Split(s, " ")
|
||||
return fields[0], fields[1:]
|
||||
}
|
||||
|
||||
// ParseDirectives extracts all directives from a list of Go files.
|
||||
func ParseDirectives(files []*ast.File, fset *token.FileSet) []Directive {
|
||||
var dirs []Directive
|
||||
for _, f := range files {
|
||||
// OPT(dh): in our old code, we skip all the comment map work if we
|
||||
// couldn't find any directives, benchmark if that's actually
|
||||
// worth doing
|
||||
cm := ast.NewCommentMap(fset, f, f.Comments)
|
||||
for node, cgs := range cm {
|
||||
for _, cg := range cgs {
|
||||
for _, c := range cg.List {
|
||||
if !strings.HasPrefix(c.Text, "//lint:") {
|
||||
continue
|
||||
}
|
||||
cmd, args := parseDirective(c.Text)
|
||||
d := Directive{
|
||||
Command: cmd,
|
||||
Arguments: args,
|
||||
Directive: c,
|
||||
Node: node,
|
||||
}
|
||||
dirs = append(dirs, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
281
vendor/honnef.co/go/tools/analysis/report/report.go
vendored
Normal file
281
vendor/honnef.co/go/tools/analysis/report/report.go
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"go/version"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/go/ast/astutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
ShortRange bool
|
||||
FilterGenerated bool
|
||||
Fixes []analysis.SuggestedFix
|
||||
Related []analysis.RelatedInformation
|
||||
MinimumLanguageVersion string
|
||||
MaximumLanguageVersion string
|
||||
MinimumStdlibVersion string
|
||||
MaximumStdlibVersion string
|
||||
}
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
func ShortRange() Option {
|
||||
return func(opts *Options) {
|
||||
opts.ShortRange = true
|
||||
}
|
||||
}
|
||||
|
||||
func FilterGenerated() Option {
|
||||
return func(opts *Options) {
|
||||
opts.FilterGenerated = true
|
||||
}
|
||||
}
|
||||
|
||||
func Fixes(fixes ...analysis.SuggestedFix) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Fixes = append(opts.Fixes, fixes...)
|
||||
}
|
||||
}
|
||||
|
||||
func Related(node Positioner, message string) Option {
|
||||
return func(opts *Options) {
|
||||
pos, end, ok := getRange(node, opts.ShortRange)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
r := analysis.RelatedInformation{
|
||||
Pos: pos,
|
||||
End: end,
|
||||
Message: message,
|
||||
}
|
||||
opts.Related = append(opts.Related, r)
|
||||
}
|
||||
}
|
||||
|
||||
func MinimumLanguageVersion(vers string) Option {
|
||||
return func(opts *Options) { opts.MinimumLanguageVersion = vers }
|
||||
}
|
||||
func MaximumLanguageVersion(vers string) Option {
|
||||
return func(opts *Options) { opts.MinimumLanguageVersion = vers }
|
||||
}
|
||||
func MinimumStdlibVersion(vers string) Option {
|
||||
return func(opts *Options) { opts.MinimumStdlibVersion = vers }
|
||||
}
|
||||
func MaximumStdlibVersion(vers string) Option {
|
||||
return func(opts *Options) { opts.MaximumStdlibVersion = vers }
|
||||
}
|
||||
|
||||
type Positioner interface {
|
||||
Pos() token.Pos
|
||||
}
|
||||
|
||||
type fullPositioner interface {
|
||||
Pos() token.Pos
|
||||
End() token.Pos
|
||||
}
|
||||
|
||||
type sourcer interface {
|
||||
Source() ast.Node
|
||||
}
|
||||
|
||||
// shortRange returns the position and end of the main component of an
|
||||
// AST node. For nodes that have no body, the short range is identical
|
||||
// to the node's Pos and End. For nodes that do have a body, the short
|
||||
// range excludes the body.
|
||||
func shortRange(node ast.Node) (pos, end token.Pos) {
|
||||
switch node := node.(type) {
|
||||
case *ast.File:
|
||||
return node.Pos(), node.Name.End()
|
||||
case *ast.CaseClause:
|
||||
return node.Pos(), node.Colon + 1
|
||||
case *ast.CommClause:
|
||||
return node.Pos(), node.Colon + 1
|
||||
case *ast.DeferStmt:
|
||||
return node.Pos(), node.Defer + token.Pos(len("defer"))
|
||||
case *ast.ExprStmt:
|
||||
return shortRange(node.X)
|
||||
case *ast.ForStmt:
|
||||
if node.Post != nil {
|
||||
return node.For, node.Post.End()
|
||||
} else if node.Cond != nil {
|
||||
return node.For, node.Cond.End()
|
||||
} else if node.Init != nil {
|
||||
// +1 to catch the semicolon, for gofmt'ed code
|
||||
return node.Pos(), node.Init.End() + 1
|
||||
} else {
|
||||
return node.Pos(), node.For + token.Pos(len("for"))
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
return node.Pos(), node.Type.End()
|
||||
case *ast.FuncLit:
|
||||
return node.Pos(), node.Type.End()
|
||||
case *ast.GoStmt:
|
||||
if _, ok := astutil.Unparen(node.Call.Fun).(*ast.FuncLit); ok {
|
||||
return node.Pos(), node.Go + token.Pos(len("go"))
|
||||
} else {
|
||||
return node.Pos(), node.End()
|
||||
}
|
||||
case *ast.IfStmt:
|
||||
return node.Pos(), node.Cond.End()
|
||||
case *ast.RangeStmt:
|
||||
return node.Pos(), node.X.End()
|
||||
case *ast.SelectStmt:
|
||||
return node.Pos(), node.Pos() + token.Pos(len("select"))
|
||||
case *ast.SwitchStmt:
|
||||
if node.Tag != nil {
|
||||
return node.Pos(), node.Tag.End()
|
||||
} else if node.Init != nil {
|
||||
// +1 to catch the semicolon, for gofmt'ed code
|
||||
return node.Pos(), node.Init.End() + 1
|
||||
} else {
|
||||
return node.Pos(), node.Pos() + token.Pos(len("switch"))
|
||||
}
|
||||
case *ast.TypeSwitchStmt:
|
||||
return node.Pos(), node.Assign.End()
|
||||
default:
|
||||
return node.Pos(), node.End()
|
||||
}
|
||||
}
|
||||
|
||||
func HasRange(node Positioner) bool {
|
||||
// we don't know if getRange will be called with shortRange set to
|
||||
// true, so make sure that both work.
|
||||
_, _, ok := getRange(node, false)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, _, ok = getRange(node, true)
|
||||
return ok
|
||||
}
|
||||
|
||||
func getRange(node Positioner, short bool) (pos, end token.Pos, ok bool) {
|
||||
switch n := node.(type) {
|
||||
case sourcer:
|
||||
s := n.Source()
|
||||
if s == nil {
|
||||
return 0, 0, false
|
||||
}
|
||||
if short {
|
||||
p, e := shortRange(s)
|
||||
return p, e, true
|
||||
}
|
||||
return s.Pos(), s.End(), true
|
||||
case fullPositioner:
|
||||
if short {
|
||||
p, e := shortRange(n)
|
||||
return p, e, true
|
||||
}
|
||||
return n.Pos(), n.End(), true
|
||||
default:
|
||||
return n.Pos(), token.NoPos, true
|
||||
}
|
||||
}
|
||||
|
||||
func Report(pass *analysis.Pass, node Positioner, message string, opts ...Option) {
|
||||
cfg := &Options{}
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
langVersion := code.LanguageVersion(pass, node)
|
||||
stdlibVersion := code.StdlibVersion(pass, node)
|
||||
if n := cfg.MaximumLanguageVersion; n != "" && version.Compare(n, langVersion) == -1 {
|
||||
return
|
||||
}
|
||||
if n := cfg.MaximumStdlibVersion; n != "" && version.Compare(n, stdlibVersion) == -1 {
|
||||
return
|
||||
}
|
||||
if n := cfg.MinimumLanguageVersion; n != "" && version.Compare(n, langVersion) == 1 {
|
||||
return
|
||||
}
|
||||
if n := cfg.MinimumStdlibVersion; n != "" && version.Compare(n, stdlibVersion) == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
file := DisplayPosition(pass.Fset, node.Pos()).Filename
|
||||
if cfg.FilterGenerated {
|
||||
m := pass.ResultOf[generated.Analyzer].(map[string]generated.Generator)
|
||||
if _, ok := m[file]; ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pos, end, ok := getRange(node, cfg.ShortRange)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("no valid position for reporting node %v", node))
|
||||
}
|
||||
d := analysis.Diagnostic{
|
||||
Pos: pos,
|
||||
End: end,
|
||||
Message: message,
|
||||
SuggestedFixes: cfg.Fixes,
|
||||
Related: cfg.Related,
|
||||
}
|
||||
pass.Report(d)
|
||||
}
|
||||
|
||||
func Render(pass *analysis.Pass, x any) string {
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, pass.Fset, x); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func RenderArgs(pass *analysis.Pass, args []ast.Expr) string {
|
||||
var ss []string
|
||||
for _, arg := range args {
|
||||
ss = append(ss, Render(pass, arg))
|
||||
}
|
||||
return strings.Join(ss, ", ")
|
||||
}
|
||||
|
||||
func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
|
||||
if p == token.NoPos {
|
||||
return token.Position{}
|
||||
}
|
||||
|
||||
// Only use the adjusted position if it points to another Go file.
|
||||
// This means we'll point to the original file for cgo files, but
|
||||
// we won't point to a YACC grammar file.
|
||||
pos := fset.PositionFor(p, false)
|
||||
adjPos := fset.PositionFor(p, true)
|
||||
|
||||
if filepath.Ext(adjPos.Filename) == ".go" {
|
||||
return adjPos
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
func Ordinal(n int) string {
|
||||
suffix := "th"
|
||||
if n < 10 || n > 20 {
|
||||
switch n % 10 {
|
||||
case 0:
|
||||
suffix = "th"
|
||||
case 1:
|
||||
suffix = "st"
|
||||
case 2:
|
||||
suffix = "nd"
|
||||
case 3:
|
||||
suffix = "rd"
|
||||
default:
|
||||
suffix = "th"
|
||||
}
|
||||
}
|
||||
|
||||
return strconv.Itoa(n) + suffix
|
||||
}
|
||||
15
vendor/honnef.co/go/tools/cmd/staticcheck/README.md
vendored
Normal file
15
vendor/honnef.co/go/tools/cmd/staticcheck/README.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# staticcheck
|
||||
|
||||
_staticcheck_ offers extensive analysis of Go code, covering a myriad
|
||||
of categories. It will detect bugs, suggest code simplifications,
|
||||
point out dead code, and more.
|
||||
|
||||
## Installation
|
||||
|
||||
See [the main README](https://github.com/dominikh/go-tools#installation) for installation instructions.
|
||||
|
||||
## Documentation
|
||||
|
||||
Detailed documentation can be found on
|
||||
[staticcheck.dev](https://staticcheck.dev/docs/).
|
||||
|
||||
45
vendor/honnef.co/go/tools/cmd/staticcheck/staticcheck.go
vendored
Normal file
45
vendor/honnef.co/go/tools/cmd/staticcheck/staticcheck.go
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// staticcheck analyses Go code and makes it better.
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"honnef.co/go/tools/lintcmd"
|
||||
"honnef.co/go/tools/lintcmd/version"
|
||||
"honnef.co/go/tools/quickfix"
|
||||
"honnef.co/go/tools/simple"
|
||||
"honnef.co/go/tools/staticcheck"
|
||||
"honnef.co/go/tools/stylecheck"
|
||||
"honnef.co/go/tools/unused"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd := lintcmd.NewCommand("staticcheck")
|
||||
cmd.SetVersion(version.Version, version.MachineVersion)
|
||||
|
||||
fs := cmd.FlagSet()
|
||||
debug := fs.String("debug.unused-graph", "", "Write unused's object graph to `file`")
|
||||
qf := fs.Bool("debug.run-quickfix-analyzers", false, "Run quickfix analyzers")
|
||||
|
||||
cmd.ParseFlags(os.Args[1:])
|
||||
|
||||
cmd.AddAnalyzers(simple.Analyzers...)
|
||||
cmd.AddAnalyzers(staticcheck.Analyzers...)
|
||||
cmd.AddAnalyzers(stylecheck.Analyzers...)
|
||||
cmd.AddAnalyzers(unused.Analyzer)
|
||||
|
||||
if *qf {
|
||||
cmd.AddAnalyzers(quickfix.Analyzers...)
|
||||
}
|
||||
|
||||
if *debug != "" {
|
||||
f, err := os.OpenFile(*debug, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
unused.Debug = f
|
||||
}
|
||||
|
||||
cmd.Run()
|
||||
}
|
||||
278
vendor/honnef.co/go/tools/config/config.go
vendored
Normal file
278
vendor/honnef.co/go/tools/config/config.go
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
// Dir looks at a list of absolute file names, which should make up a
|
||||
// single package, and returns the path of the directory that may
|
||||
// contain a staticcheck.conf file. It returns the empty string if no
|
||||
// such directory could be determined, for example because all files
|
||||
// were located in Go's build cache.
|
||||
func Dir(files []string) string {
|
||||
if len(files) == 0 {
|
||||
return ""
|
||||
}
|
||||
cache, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
cache = ""
|
||||
}
|
||||
var path string
|
||||
for _, p := range files {
|
||||
// FIXME(dh): using strings.HasPrefix isn't technically
|
||||
// correct, but it should be good enough for now.
|
||||
if cache != "" && strings.HasPrefix(p, cache) {
|
||||
// File in the build cache of the standard Go build system
|
||||
continue
|
||||
}
|
||||
path = p
|
||||
break
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
// The package only consists of generated files.
|
||||
return ""
|
||||
}
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
return dir
|
||||
}
|
||||
|
||||
func dirAST(files []*ast.File, fset *token.FileSet) string {
|
||||
names := make([]string, len(files))
|
||||
for i, f := range files {
|
||||
names[i] = fset.PositionFor(f.Pos(), true).Filename
|
||||
}
|
||||
return Dir(names)
|
||||
}
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "config",
|
||||
Doc: "loads configuration for the current package tree",
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
dir := dirAST(pass.Files, pass.Fset)
|
||||
if dir == "" {
|
||||
cfg := DefaultConfig
|
||||
return &cfg, nil
|
||||
}
|
||||
cfg, err := Load(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading staticcheck.conf: %s", err)
|
||||
}
|
||||
return &cfg, nil
|
||||
},
|
||||
RunDespiteErrors: true,
|
||||
ResultType: reflect.TypeFor[*Config](),
|
||||
}
|
||||
|
||||
func For(pass *analysis.Pass) *Config {
|
||||
return pass.ResultOf[Analyzer].(*Config)
|
||||
}
|
||||
|
||||
func mergeLists(a, b []string) []string {
|
||||
out := make([]string, 0, len(a)+len(b))
|
||||
for _, el := range b {
|
||||
if el == "inherit" {
|
||||
out = append(out, a...)
|
||||
} else {
|
||||
out = append(out, el)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeList(list []string) []string {
|
||||
if len(list) > 1 {
|
||||
nlist := make([]string, 0, len(list))
|
||||
nlist = append(nlist, list[0])
|
||||
for i, el := range list[1:] {
|
||||
if el != list[i] {
|
||||
nlist = append(nlist, el)
|
||||
}
|
||||
}
|
||||
list = nlist
|
||||
}
|
||||
|
||||
for _, el := range list {
|
||||
if el == "inherit" {
|
||||
// This should never happen, because the default config
|
||||
// should not use "inherit"
|
||||
panic(`unresolved "inherit"`)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (cfg Config) Merge(ocfg Config) Config {
|
||||
if ocfg.Checks != nil {
|
||||
cfg.Checks = mergeLists(cfg.Checks, ocfg.Checks)
|
||||
}
|
||||
if ocfg.Initialisms != nil {
|
||||
cfg.Initialisms = mergeLists(cfg.Initialisms, ocfg.Initialisms)
|
||||
}
|
||||
if ocfg.DotImportWhitelist != nil {
|
||||
cfg.DotImportWhitelist = mergeLists(cfg.DotImportWhitelist, ocfg.DotImportWhitelist)
|
||||
}
|
||||
if ocfg.HTTPStatusCodeWhitelist != nil {
|
||||
cfg.HTTPStatusCodeWhitelist = mergeLists(cfg.HTTPStatusCodeWhitelist, ocfg.HTTPStatusCodeWhitelist)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// TODO(dh): this implementation makes it impossible for external
|
||||
// clients to add their own checkers with configuration. At the
|
||||
// moment, we don't really care about that; we don't encourage
|
||||
// that people use this package. In the future, we may. The
|
||||
// obvious solution would be using map[string]interface{}, but
|
||||
// that's obviously subpar.
|
||||
|
||||
Checks []string `toml:"checks"`
|
||||
Initialisms []string `toml:"initialisms"`
|
||||
DotImportWhitelist []string `toml:"dot_import_whitelist"`
|
||||
HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"`
|
||||
}
|
||||
|
||||
func (c Config) String() string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
fmt.Fprintf(buf, "Checks: %#v\n", c.Checks)
|
||||
fmt.Fprintf(buf, "Initialisms: %#v\n", c.Initialisms)
|
||||
fmt.Fprintf(buf, "DotImportWhitelist: %#v\n", c.DotImportWhitelist)
|
||||
fmt.Fprintf(buf, "HTTPStatusCodeWhitelist: %#v", c.HTTPStatusCodeWhitelist)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// DefaultConfig is the default configuration.
|
||||
// Its initial value describes the majority of the default configuration,
|
||||
// but the Checks field can be updated at runtime based on the analyzers being used, to disable non-default checks.
|
||||
// For cmd/staticcheck, this is handled by (*lintcmd.Command).Run.
|
||||
//
|
||||
// Note that DefaultConfig shouldn't be modified while analyzers are executing.
|
||||
var DefaultConfig = Config{
|
||||
Checks: []string{"all"},
|
||||
Initialisms: []string{
|
||||
"ACL", "API", "ASCII", "CPU", "CSS", "DNS",
|
||||
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
|
||||
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
|
||||
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
|
||||
"UDP", "UI", "GID", "UID", "UUID", "URI",
|
||||
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
|
||||
"XSS", "SIP", "RTP", "AMQP", "DB", "TS",
|
||||
},
|
||||
DotImportWhitelist: []string{
|
||||
"simd/archsimd",
|
||||
"github.com/mmcloughlin/avo/build",
|
||||
"github.com/mmcloughlin/avo/operand",
|
||||
"github.com/mmcloughlin/avo/reg",
|
||||
},
|
||||
HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"},
|
||||
}
|
||||
|
||||
const ConfigName = "staticcheck.conf"
|
||||
|
||||
type ParseError struct {
|
||||
Filename string
|
||||
toml.ParseError
|
||||
}
|
||||
|
||||
func parseConfigs(dir string) ([]Config, error) {
|
||||
var out []Config
|
||||
|
||||
// TODO(dh): consider stopping at the GOPATH/module boundary
|
||||
for dir != "" {
|
||||
path := filepath.Join(dir, ConfigName)
|
||||
fi, err := os.Stat(path)
|
||||
if os.IsNotExist(err) || (err == nil && !fi.Mode().IsRegular()) {
|
||||
// walk up
|
||||
ndir := filepath.Dir(dir)
|
||||
if ndir == dir {
|
||||
break
|
||||
}
|
||||
dir = ndir
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// There is a small TOCTOU window here, but we're fine with reporting an
|
||||
// error if the source tree is modified concurrently in weird ways while
|
||||
// running Staticcheck.
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
_, err = toml.NewDecoder(f).Decode(&cfg)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
if err, ok := err.(toml.ParseError); ok {
|
||||
return nil, ParseError{
|
||||
Filename: filepath.Join(dir, ConfigName),
|
||||
ParseError: err,
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, cfg)
|
||||
ndir := filepath.Dir(dir)
|
||||
if ndir == dir {
|
||||
break
|
||||
}
|
||||
dir = ndir
|
||||
}
|
||||
out = append(out, DefaultConfig)
|
||||
if len(out) < 2 {
|
||||
return out, nil
|
||||
}
|
||||
for i := 0; i < len(out)/2; i++ {
|
||||
out[i], out[len(out)-1-i] = out[len(out)-1-i], out[i]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func mergeConfigs(confs []Config) Config {
|
||||
if len(confs) == 0 {
|
||||
// This shouldn't happen because we always have at least a
|
||||
// default config.
|
||||
panic("trying to merge zero configs")
|
||||
}
|
||||
if len(confs) == 1 {
|
||||
return confs[0]
|
||||
}
|
||||
conf := confs[0]
|
||||
for _, oconf := range confs[1:] {
|
||||
conf = conf.Merge(oconf)
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
func Load(dir string) (Config, error) {
|
||||
confs, err := parseConfigs(dir)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
conf := mergeConfigs(confs)
|
||||
|
||||
conf.Checks = normalizeList(conf.Checks)
|
||||
conf.Initialisms = normalizeList(conf.Initialisms)
|
||||
conf.DotImportWhitelist = normalizeList(conf.DotImportWhitelist)
|
||||
conf.HTTPStatusCodeWhitelist = normalizeList(conf.HTTPStatusCodeWhitelist)
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
15
vendor/honnef.co/go/tools/config/example.conf
vendored
Normal file
15
vendor/honnef.co/go/tools/config/example.conf
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
checks = ["all", "-SA9003", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1023"]
|
||||
initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS",
|
||||
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
|
||||
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
|
||||
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
|
||||
"UDP", "UI", "GID", "UID", "UUID", "URI",
|
||||
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
|
||||
"XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
|
||||
dot_import_whitelist = [
|
||||
"simd/archsimd",
|
||||
"github.com/mmcloughlin/avo/build",
|
||||
"github.com/mmcloughlin/avo/operand",
|
||||
"github.com/mmcloughlin/avo/reg",
|
||||
]
|
||||
http_status_code_whitelist = ["200", "400", "404", "500"]
|
||||
20
vendor/honnef.co/go/tools/go/ast/astutil/upstream.go
vendored
Normal file
20
vendor/honnef.co/go/tools/go/ast/astutil/upstream.go
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package astutil
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
_ "unsafe"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
type Cursor = astutil.Cursor
|
||||
type ApplyFunc = astutil.ApplyFunc
|
||||
|
||||
func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) {
|
||||
return astutil.Apply(root, pre, post)
|
||||
}
|
||||
|
||||
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) {
|
||||
return astutil.PathEnclosingInterval(root, start, end)
|
||||
}
|
||||
473
vendor/honnef.co/go/tools/go/ast/astutil/util.go
vendored
Normal file
473
vendor/honnef.co/go/tools/go/ast/astutil/util.go
vendored
Normal file
@@ -0,0 +1,473 @@
|
||||
package astutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
func IsIdent(expr ast.Expr, ident string) bool {
|
||||
id, ok := expr.(*ast.Ident)
|
||||
return ok && id.Name == ident
|
||||
}
|
||||
|
||||
// isBlank returns whether id is the blank identifier "_".
|
||||
// If id == nil, the answer is false.
|
||||
func IsBlank(id ast.Expr) bool {
|
||||
ident, _ := id.(*ast.Ident)
|
||||
return ident != nil && ident.Name == "_"
|
||||
}
|
||||
|
||||
// Deprecated: use code.IsIntegerLiteral instead.
|
||||
func IsIntLiteral(expr ast.Expr, literal string) bool {
|
||||
lit, ok := expr.(*ast.BasicLit)
|
||||
return ok && lit.Kind == token.INT && lit.Value == literal
|
||||
}
|
||||
|
||||
// Deprecated: use IsIntLiteral instead
|
||||
func IsZero(expr ast.Expr) bool {
|
||||
return IsIntLiteral(expr, "0")
|
||||
}
|
||||
|
||||
func Preamble(f *ast.File) string {
|
||||
cutoff := f.Package
|
||||
if f.Doc != nil {
|
||||
cutoff = f.Doc.Pos()
|
||||
}
|
||||
var out []string
|
||||
for _, cmt := range f.Comments {
|
||||
if cmt.Pos() >= cutoff {
|
||||
break
|
||||
}
|
||||
out = append(out, cmt.Text())
|
||||
}
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec {
|
||||
if len(specs) == 0 {
|
||||
return nil
|
||||
}
|
||||
groups := make([][]ast.Spec, 1)
|
||||
groups[0] = append(groups[0], specs[0])
|
||||
|
||||
for _, spec := range specs[1:] {
|
||||
g := groups[len(groups)-1]
|
||||
if fset.PositionFor(spec.Pos(), false).Line-1 !=
|
||||
fset.PositionFor(g[len(g)-1].End(), false).Line {
|
||||
|
||||
groups = append(groups, nil)
|
||||
}
|
||||
|
||||
groups[len(groups)-1] = append(groups[len(groups)-1], spec)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
// Unparen returns e with any enclosing parentheses stripped.
|
||||
func Unparen(e ast.Expr) ast.Expr {
|
||||
for {
|
||||
p, ok := e.(*ast.ParenExpr)
|
||||
if !ok {
|
||||
return e
|
||||
}
|
||||
e = p.X
|
||||
}
|
||||
}
|
||||
|
||||
// CopyExpr creates a deep copy of an expression.
|
||||
// It doesn't support copying FuncLits and returns ok == false when encountering one.
|
||||
func CopyExpr(node ast.Expr) (ast.Expr, bool) {
|
||||
switch node := node.(type) {
|
||||
case *ast.BasicLit:
|
||||
cp := *node
|
||||
return &cp, true
|
||||
case *ast.BinaryExpr:
|
||||
cp := *node
|
||||
var ok1, ok2 bool
|
||||
cp.X, ok1 = CopyExpr(cp.X)
|
||||
cp.Y, ok2 = CopyExpr(cp.Y)
|
||||
return &cp, ok1 && ok2
|
||||
case *ast.CallExpr:
|
||||
var ok bool
|
||||
cp := *node
|
||||
cp.Fun, ok = CopyExpr(cp.Fun)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
cp.Args = make([]ast.Expr, len(node.Args))
|
||||
for i, v := range node.Args {
|
||||
cp.Args[i], ok = CopyExpr(v)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return &cp, true
|
||||
case *ast.CompositeLit:
|
||||
var ok bool
|
||||
cp := *node
|
||||
cp.Type, ok = CopyExpr(cp.Type)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
cp.Elts = make([]ast.Expr, len(node.Elts))
|
||||
for i, v := range node.Elts {
|
||||
cp.Elts[i], ok = CopyExpr(v)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return &cp, true
|
||||
case *ast.Ident:
|
||||
cp := *node
|
||||
return &cp, true
|
||||
case *ast.IndexExpr:
|
||||
var ok1, ok2 bool
|
||||
cp := *node
|
||||
cp.X, ok1 = CopyExpr(cp.X)
|
||||
cp.Index, ok2 = CopyExpr(cp.Index)
|
||||
return &cp, ok1 && ok2
|
||||
case *ast.IndexListExpr:
|
||||
var ok bool
|
||||
cp := *node
|
||||
cp.X, ok = CopyExpr(cp.X)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
for i, v := range node.Indices {
|
||||
cp.Indices[i], ok = CopyExpr(v)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return &cp, true
|
||||
case *ast.KeyValueExpr:
|
||||
var ok1, ok2 bool
|
||||
cp := *node
|
||||
cp.Key, ok1 = CopyExpr(cp.Key)
|
||||
cp.Value, ok2 = CopyExpr(cp.Value)
|
||||
return &cp, ok1 && ok2
|
||||
case *ast.ParenExpr:
|
||||
var ok bool
|
||||
cp := *node
|
||||
cp.X, ok = CopyExpr(cp.X)
|
||||
return &cp, ok
|
||||
case *ast.SelectorExpr:
|
||||
var ok bool
|
||||
cp := *node
|
||||
cp.X, ok = CopyExpr(cp.X)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
sel, ok := CopyExpr(cp.Sel)
|
||||
if !ok {
|
||||
// this is impossible
|
||||
return nil, false
|
||||
}
|
||||
cp.Sel = sel.(*ast.Ident)
|
||||
return &cp, true
|
||||
case *ast.SliceExpr:
|
||||
var ok1, ok2, ok3, ok4 bool
|
||||
cp := *node
|
||||
cp.X, ok1 = CopyExpr(cp.X)
|
||||
cp.Low, ok2 = CopyExpr(cp.Low)
|
||||
cp.High, ok3 = CopyExpr(cp.High)
|
||||
cp.Max, ok4 = CopyExpr(cp.Max)
|
||||
return &cp, ok1 && ok2 && ok3 && ok4
|
||||
case *ast.StarExpr:
|
||||
var ok bool
|
||||
cp := *node
|
||||
cp.X, ok = CopyExpr(cp.X)
|
||||
return &cp, ok
|
||||
case *ast.TypeAssertExpr:
|
||||
var ok1, ok2 bool
|
||||
cp := *node
|
||||
cp.X, ok1 = CopyExpr(cp.X)
|
||||
cp.Type, ok2 = CopyExpr(cp.Type)
|
||||
return &cp, ok1 && ok2
|
||||
case *ast.UnaryExpr:
|
||||
var ok bool
|
||||
cp := *node
|
||||
cp.X, ok = CopyExpr(cp.X)
|
||||
return &cp, ok
|
||||
case *ast.MapType:
|
||||
var ok1, ok2 bool
|
||||
cp := *node
|
||||
cp.Key, ok1 = CopyExpr(cp.Key)
|
||||
cp.Value, ok2 = CopyExpr(cp.Value)
|
||||
return &cp, ok1 && ok2
|
||||
case *ast.ArrayType:
|
||||
var ok1, ok2 bool
|
||||
cp := *node
|
||||
cp.Len, ok1 = CopyExpr(cp.Len)
|
||||
cp.Elt, ok2 = CopyExpr(cp.Elt)
|
||||
return &cp, ok1 && ok2
|
||||
case *ast.Ellipsis:
|
||||
var ok bool
|
||||
cp := *node
|
||||
cp.Elt, ok = CopyExpr(cp.Elt)
|
||||
return &cp, ok
|
||||
case *ast.InterfaceType:
|
||||
cp := *node
|
||||
return &cp, true
|
||||
case *ast.StructType:
|
||||
cp := *node
|
||||
return &cp, true
|
||||
case *ast.FuncLit, *ast.FuncType:
|
||||
// TODO(dh): implement copying of function literals and types.
|
||||
return nil, false
|
||||
case *ast.ChanType:
|
||||
var ok bool
|
||||
cp := *node
|
||||
cp.Value, ok = CopyExpr(cp.Value)
|
||||
return &cp, ok
|
||||
case nil:
|
||||
return nil, true
|
||||
default:
|
||||
panic(fmt.Sprintf("unreachable: %T", node))
|
||||
}
|
||||
}
|
||||
|
||||
func Equal(a, b ast.Node) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
if reflect.TypeOf(a) != reflect.TypeOf(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch a := a.(type) {
|
||||
case *ast.BasicLit:
|
||||
b := b.(*ast.BasicLit)
|
||||
return a.Kind == b.Kind && a.Value == b.Value
|
||||
case *ast.BinaryExpr:
|
||||
b := b.(*ast.BinaryExpr)
|
||||
return Equal(a.X, b.X) && a.Op == b.Op && Equal(a.Y, b.Y)
|
||||
case *ast.CallExpr:
|
||||
b := b.(*ast.CallExpr)
|
||||
if len(a.Args) != len(b.Args) {
|
||||
return false
|
||||
}
|
||||
for i, arg := range a.Args {
|
||||
if !Equal(arg, b.Args[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return Equal(a.Fun, b.Fun) &&
|
||||
(a.Ellipsis == token.NoPos && b.Ellipsis == token.NoPos || a.Ellipsis != token.NoPos && b.Ellipsis != token.NoPos)
|
||||
case *ast.CompositeLit:
|
||||
b := b.(*ast.CompositeLit)
|
||||
if len(a.Elts) != len(b.Elts) {
|
||||
return false
|
||||
}
|
||||
for i, elt := range b.Elts {
|
||||
if !Equal(elt, b.Elts[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return Equal(a.Type, b.Type) && a.Incomplete == b.Incomplete
|
||||
case *ast.Ident:
|
||||
b := b.(*ast.Ident)
|
||||
return a.Name == b.Name
|
||||
case *ast.IndexExpr:
|
||||
b := b.(*ast.IndexExpr)
|
||||
return Equal(a.X, b.X) && Equal(a.Index, b.Index)
|
||||
case *ast.IndexListExpr:
|
||||
b := b.(*ast.IndexListExpr)
|
||||
if len(a.Indices) != len(b.Indices) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a.Indices {
|
||||
if !Equal(v, b.Indices[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return Equal(a.X, b.X)
|
||||
case *ast.KeyValueExpr:
|
||||
b := b.(*ast.KeyValueExpr)
|
||||
return Equal(a.Key, b.Key) && Equal(a.Value, b.Value)
|
||||
case *ast.ParenExpr:
|
||||
b := b.(*ast.ParenExpr)
|
||||
return Equal(a.X, b.X)
|
||||
case *ast.SelectorExpr:
|
||||
b := b.(*ast.SelectorExpr)
|
||||
return Equal(a.X, b.X) && Equal(a.Sel, b.Sel)
|
||||
case *ast.SliceExpr:
|
||||
b := b.(*ast.SliceExpr)
|
||||
return Equal(a.X, b.X) && Equal(a.Low, b.Low) && Equal(a.High, b.High) && Equal(a.Max, b.Max) && a.Slice3 == b.Slice3
|
||||
case *ast.StarExpr:
|
||||
b := b.(*ast.StarExpr)
|
||||
return Equal(a.X, b.X)
|
||||
case *ast.TypeAssertExpr:
|
||||
b := b.(*ast.TypeAssertExpr)
|
||||
return Equal(a.X, b.X) && Equal(a.Type, b.Type)
|
||||
case *ast.UnaryExpr:
|
||||
b := b.(*ast.UnaryExpr)
|
||||
return a.Op == b.Op && Equal(a.X, b.X)
|
||||
case *ast.MapType:
|
||||
b := b.(*ast.MapType)
|
||||
return Equal(a.Key, b.Key) && Equal(a.Value, b.Value)
|
||||
case *ast.ArrayType:
|
||||
b := b.(*ast.ArrayType)
|
||||
return Equal(a.Len, b.Len) && Equal(a.Elt, b.Elt)
|
||||
case *ast.Ellipsis:
|
||||
b := b.(*ast.Ellipsis)
|
||||
return Equal(a.Elt, b.Elt)
|
||||
case *ast.InterfaceType:
|
||||
b := b.(*ast.InterfaceType)
|
||||
return a.Incomplete == b.Incomplete && Equal(a.Methods, b.Methods)
|
||||
case *ast.StructType:
|
||||
b := b.(*ast.StructType)
|
||||
return a.Incomplete == b.Incomplete && Equal(a.Fields, b.Fields)
|
||||
case *ast.FuncLit:
|
||||
// TODO(dh): support function literals
|
||||
return false
|
||||
case *ast.ChanType:
|
||||
b := b.(*ast.ChanType)
|
||||
return a.Dir == b.Dir && (a.Arrow == token.NoPos && b.Arrow == token.NoPos || a.Arrow != token.NoPos && b.Arrow != token.NoPos)
|
||||
case *ast.FieldList:
|
||||
b := b.(*ast.FieldList)
|
||||
if len(a.List) != len(b.List) {
|
||||
return false
|
||||
}
|
||||
for i, fieldA := range a.List {
|
||||
if !Equal(fieldA, b.List[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *ast.Field:
|
||||
b := b.(*ast.Field)
|
||||
if len(a.Names) != len(b.Names) {
|
||||
return false
|
||||
}
|
||||
for j, name := range a.Names {
|
||||
if !Equal(name, b.Names[j]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !Equal(a.Type, b.Type) || !Equal(a.Tag, b.Tag) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
panic(fmt.Sprintf("unreachable: %T", a))
|
||||
}
|
||||
}
|
||||
|
||||
func NegateDeMorgan(expr ast.Expr, recursive bool) ast.Expr {
|
||||
switch expr := expr.(type) {
|
||||
case *ast.BinaryExpr:
|
||||
var out ast.BinaryExpr
|
||||
switch expr.Op {
|
||||
case token.EQL:
|
||||
out.X = expr.X
|
||||
out.Op = token.NEQ
|
||||
out.Y = expr.Y
|
||||
case token.LSS:
|
||||
out.X = expr.X
|
||||
out.Op = token.GEQ
|
||||
out.Y = expr.Y
|
||||
case token.GTR:
|
||||
out.X = expr.X
|
||||
out.Op = token.LEQ
|
||||
out.Y = expr.Y
|
||||
case token.NEQ:
|
||||
out.X = expr.X
|
||||
out.Op = token.EQL
|
||||
out.Y = expr.Y
|
||||
case token.LEQ:
|
||||
out.X = expr.X
|
||||
out.Op = token.GTR
|
||||
out.Y = expr.Y
|
||||
case token.GEQ:
|
||||
out.X = expr.X
|
||||
out.Op = token.LSS
|
||||
out.Y = expr.Y
|
||||
|
||||
case token.LAND:
|
||||
out.X = NegateDeMorgan(expr.X, recursive)
|
||||
out.Op = token.LOR
|
||||
out.Y = NegateDeMorgan(expr.Y, recursive)
|
||||
case token.LOR:
|
||||
out.X = NegateDeMorgan(expr.X, recursive)
|
||||
out.Op = token.LAND
|
||||
out.Y = NegateDeMorgan(expr.Y, recursive)
|
||||
}
|
||||
return &out
|
||||
|
||||
case *ast.ParenExpr:
|
||||
if recursive {
|
||||
return &ast.ParenExpr{
|
||||
X: NegateDeMorgan(expr.X, recursive),
|
||||
}
|
||||
} else {
|
||||
return &ast.UnaryExpr{
|
||||
Op: token.NOT,
|
||||
X: expr,
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
if expr.Op == token.NOT {
|
||||
return expr.X
|
||||
} else {
|
||||
return &ast.UnaryExpr{
|
||||
Op: token.NOT,
|
||||
X: expr,
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return &ast.UnaryExpr{
|
||||
Op: token.NOT,
|
||||
X: expr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SimplifyParentheses(node ast.Expr) ast.Expr {
|
||||
var changed bool
|
||||
// XXX accept list of ops to operate on
|
||||
// XXX copy AST node, don't modify in place
|
||||
post := func(c *astutil.Cursor) bool {
|
||||
out := c.Node()
|
||||
if paren, ok := c.Node().(*ast.ParenExpr); ok {
|
||||
out = paren.X
|
||||
}
|
||||
|
||||
if binop, ok := out.(*ast.BinaryExpr); ok {
|
||||
if right, ok := binop.Y.(*ast.BinaryExpr); ok && binop.Op == right.Op {
|
||||
// XXX also check that Op is associative
|
||||
|
||||
root := binop
|
||||
pivot := root.Y.(*ast.BinaryExpr)
|
||||
root.Y = pivot.X
|
||||
pivot.X = root
|
||||
root = pivot
|
||||
out = root
|
||||
}
|
||||
}
|
||||
|
||||
if out != c.Node() {
|
||||
changed = true
|
||||
c.Replace(out)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
for changed = true; changed; {
|
||||
changed = false
|
||||
node = astutil.Apply(node, nil, post).(ast.Expr)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
5
vendor/honnef.co/go/tools/go/buildid/UPSTREAM
vendored
Normal file
5
vendor/honnef.co/go/tools/go/buildid/UPSTREAM
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
This package extracts buildid.go and note.go from cmd/internal/buildid/.
|
||||
|
||||
We have modified it to remove support for AIX big archive files, to cut down on our dependencies.
|
||||
|
||||
The last upstream commit we've looked at was: d3ddc4854429185e6e06ca1f7628bb790404abb5
|
||||
238
vendor/honnef.co/go/tools/go/buildid/buildid.go
vendored
Normal file
238
vendor/honnef.co/go/tools/go/buildid/buildid.go
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package buildid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errBuildIDMalformed = fmt.Errorf("malformed object file")
|
||||
|
||||
var (
|
||||
bangArch = []byte("!<arch>")
|
||||
pkgdef = []byte("__.PKGDEF")
|
||||
goobject = []byte("go object ")
|
||||
buildid = []byte("build id ")
|
||||
)
|
||||
|
||||
// ReadFile reads the build ID from an archive or executable file.
|
||||
func ReadFile(name string) (id string, err error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
buf := make([]byte, 8)
|
||||
if _, err := f.ReadAt(buf, 0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if string(buf) != "!<arch>\n" {
|
||||
if string(buf) == "<bigaf>\n" {
|
||||
return "", errors.New("unsupported")
|
||||
}
|
||||
return readBinary(name, f)
|
||||
}
|
||||
|
||||
// Read just enough of the target to fetch the build ID.
|
||||
// The archive is expected to look like:
|
||||
//
|
||||
// !<arch>
|
||||
// __.PKGDEF 0 0 0 644 7955 `
|
||||
// go object darwin amd64 devel X:none
|
||||
// build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224"
|
||||
//
|
||||
// The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none).
|
||||
// Reading the first 1024 bytes should be plenty.
|
||||
data := make([]byte, 1024)
|
||||
n, err := io.ReadFull(f, data)
|
||||
if err != nil && n == 0 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tryGccgo := func() (string, error) {
|
||||
return readGccgoArchive(name, f)
|
||||
}
|
||||
|
||||
// Archive header.
|
||||
for i := 0; ; i++ { // returns during i==3
|
||||
j := bytes.IndexByte(data, '\n')
|
||||
if j < 0 {
|
||||
return tryGccgo()
|
||||
}
|
||||
line := data[:j]
|
||||
data = data[j+1:]
|
||||
switch i {
|
||||
case 0:
|
||||
if !bytes.Equal(line, bangArch) {
|
||||
return tryGccgo()
|
||||
}
|
||||
case 1:
|
||||
if !bytes.HasPrefix(line, pkgdef) {
|
||||
return tryGccgo()
|
||||
}
|
||||
case 2:
|
||||
if !bytes.HasPrefix(line, goobject) {
|
||||
return tryGccgo()
|
||||
}
|
||||
case 3:
|
||||
if !bytes.HasPrefix(line, buildid) {
|
||||
// Found the object header, just doesn't have a build id line.
|
||||
// Treat as successful, with empty build id.
|
||||
return "", nil
|
||||
}
|
||||
id, err := strconv.Unquote(string(line[len(buildid):]))
|
||||
if err != nil {
|
||||
return tryGccgo()
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readGccgoArchive tries to parse the archive as a standard Unix
|
||||
// archive file, and fetch the build ID from the _buildid.o entry.
|
||||
// The _buildid.o entry is written by (*Builder).gccgoBuildIDELFFile
|
||||
// in cmd/go/internal/work/exec.go.
|
||||
func readGccgoArchive(name string, f *os.File) (string, error) {
|
||||
bad := func() (string, error) {
|
||||
return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
|
||||
}
|
||||
|
||||
off := int64(8)
|
||||
for {
|
||||
if _, err := f.Seek(off, io.SeekStart); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO(iant): Make a debug/ar package, and use it
|
||||
// here and in cmd/link.
|
||||
var hdr [60]byte
|
||||
if _, err := io.ReadFull(f, hdr[:]); err != nil {
|
||||
if err == io.EOF {
|
||||
// No more entries, no build ID.
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
off += 60
|
||||
|
||||
sizeStr := strings.TrimSpace(string(hdr[48:58]))
|
||||
size, err := strconv.ParseInt(sizeStr, 0, 64)
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(string(hdr[:16]))
|
||||
if name == "_buildid.o/" {
|
||||
sr := io.NewSectionReader(f, off, size)
|
||||
e, err := elf.NewFile(sr)
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
s := e.Section(".go.buildid")
|
||||
if s == nil {
|
||||
return bad()
|
||||
}
|
||||
data, err := s.Data()
|
||||
if err != nil {
|
||||
return bad()
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
off += size
|
||||
if off&1 != 0 {
|
||||
off++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
goBuildPrefix = []byte("\xff Go build ID: \"")
|
||||
goBuildEnd = []byte("\"\n \xff")
|
||||
|
||||
elfPrefix = []byte("\x7fELF")
|
||||
|
||||
machoPrefixes = [][]byte{
|
||||
{0xfe, 0xed, 0xfa, 0xce},
|
||||
{0xfe, 0xed, 0xfa, 0xcf},
|
||||
{0xce, 0xfa, 0xed, 0xfe},
|
||||
{0xcf, 0xfa, 0xed, 0xfe},
|
||||
}
|
||||
)
|
||||
|
||||
var readSize = 32 * 1024 // changed for testing
|
||||
|
||||
// readBinary reads the build ID from a binary.
|
||||
//
|
||||
// ELF binaries store the build ID in a proper PT_NOTE section.
|
||||
//
|
||||
// Other binary formats are not so flexible. For those, the linker
|
||||
// stores the build ID as non-instruction bytes at the very beginning
|
||||
// of the text segment, which should appear near the beginning
|
||||
// of the file. This is clumsy but fairly portable. Custom locations
|
||||
// can be added for other binary types as needed, like we did for ELF.
|
||||
func readBinary(name string, f *os.File) (id string, err error) {
|
||||
// Read the first 32 kB of the binary file.
|
||||
// That should be enough to find the build ID.
|
||||
// In ELF files, the build ID is in the leading headers,
|
||||
// which are typically less than 4 kB, not to mention 32 kB.
|
||||
// In Mach-O files, there's no limit, so we have to parse the file.
|
||||
// On other systems, we're trying to read enough that
|
||||
// we get the beginning of the text segment in the read.
|
||||
// The offset where the text segment begins in a hello
|
||||
// world compiled for each different object format today:
|
||||
//
|
||||
// Plan 9: 0x20
|
||||
// Windows: 0x600
|
||||
//
|
||||
data := make([]byte, readSize)
|
||||
_, err = io.ReadFull(f, data)
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(data, elfPrefix) {
|
||||
return readELF(name, f, data)
|
||||
}
|
||||
for _, m := range machoPrefixes {
|
||||
if bytes.HasPrefix(data, m) {
|
||||
return readMacho(name, f, data)
|
||||
}
|
||||
}
|
||||
return readRaw(name, data)
|
||||
}
|
||||
|
||||
// readRaw finds the raw build ID stored in text segment data.
|
||||
func readRaw(name string, data []byte) (id string, err error) {
|
||||
i := bytes.Index(data, goBuildPrefix)
|
||||
if i < 0 {
|
||||
// Missing. Treat as successful but build ID empty.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd)
|
||||
if j < 0 {
|
||||
return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
|
||||
}
|
||||
|
||||
quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1]
|
||||
id, err = strconv.Unquote(string(quoted))
|
||||
if err != nil {
|
||||
return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
204
vendor/honnef.co/go/tools/go/buildid/note.go
vendored
Normal file
204
vendor/honnef.co/go/tools/go/buildid/note.go
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package buildid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func readAligned4(r io.Reader, sz int32) ([]byte, error) {
|
||||
full := (sz + 3) &^ 3
|
||||
data := make([]byte, full)
|
||||
_, err := io.ReadFull(r, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = data[:sz]
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func ReadELFNote(filename, name string, typ int32) ([]byte, error) {
|
||||
f, err := elf.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
for _, sect := range f.Sections {
|
||||
if sect.Type != elf.SHT_NOTE {
|
||||
continue
|
||||
}
|
||||
r := sect.Open()
|
||||
for {
|
||||
var namesize, descsize, noteType int32
|
||||
err = binary.Read(r, f.ByteOrder, &namesize)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("read namesize failed: %v", err)
|
||||
}
|
||||
err = binary.Read(r, f.ByteOrder, &descsize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read descsize failed: %v", err)
|
||||
}
|
||||
err = binary.Read(r, f.ByteOrder, ¬eType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read type failed: %v", err)
|
||||
}
|
||||
noteName, err := readAligned4(r, namesize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read name failed: %v", err)
|
||||
}
|
||||
desc, err := readAligned4(r, descsize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read desc failed: %v", err)
|
||||
}
|
||||
if name == string(noteName) && typ == noteType {
|
||||
return desc, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var elfGoNote = []byte("Go\x00\x00")
|
||||
var elfGNUNote = []byte("GNU\x00")
|
||||
|
||||
// The Go build ID is stored in a note described by an ELF PT_NOTE prog
|
||||
// header. The caller has already opened filename, to get f, and read
|
||||
// at least 4 kB out, in data.
|
||||
func readELF(name string, f *os.File, data []byte) (buildid string, err error) {
|
||||
// Assume the note content is in the data, already read.
|
||||
// Rewrite the ELF header to set shnum to 0, so that we can pass
|
||||
// the data to elf.NewFile and it will decode the Prog list but not
|
||||
// try to read the section headers and the string table from disk.
|
||||
// That's a waste of I/O when all we care about is the Prog list
|
||||
// and the one ELF note.
|
||||
switch elf.Class(data[elf.EI_CLASS]) {
|
||||
case elf.ELFCLASS32:
|
||||
data[48] = 0
|
||||
data[49] = 0
|
||||
case elf.ELFCLASS64:
|
||||
data[60] = 0
|
||||
data[61] = 0
|
||||
}
|
||||
|
||||
const elfGoBuildIDTag = 4
|
||||
const gnuBuildIDTag = 3
|
||||
|
||||
ef, err := elf.NewFile(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return "", &os.PathError{Path: name, Op: "parse", Err: err}
|
||||
}
|
||||
var gnu string
|
||||
for _, p := range ef.Progs {
|
||||
if p.Type != elf.PT_NOTE || p.Filesz < 16 {
|
||||
continue
|
||||
}
|
||||
|
||||
var note []byte
|
||||
if p.Off+p.Filesz < uint64(len(data)) {
|
||||
note = data[p.Off : p.Off+p.Filesz]
|
||||
} else {
|
||||
// For some linkers, such as the Solaris linker,
|
||||
// the buildid may not be found in data (which
|
||||
// likely contains the first 16kB of the file)
|
||||
// or even the first few megabytes of the file
|
||||
// due to differences in note segment placement;
|
||||
// in that case, extract the note data manually.
|
||||
_, err = f.Seek(int64(p.Off), io.SeekStart)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
note = make([]byte, p.Filesz)
|
||||
_, err = io.ReadFull(f, note)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
filesz := p.Filesz
|
||||
off := p.Off
|
||||
for filesz >= 16 {
|
||||
nameSize := ef.ByteOrder.Uint32(note)
|
||||
valSize := ef.ByteOrder.Uint32(note[4:])
|
||||
tag := ef.ByteOrder.Uint32(note[8:])
|
||||
nname := note[12:16]
|
||||
if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(nname, elfGoNote) {
|
||||
return string(note[16 : 16+valSize]), nil
|
||||
}
|
||||
|
||||
if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == gnuBuildIDTag && bytes.Equal(nname, elfGNUNote) {
|
||||
gnu = string(note[16 : 16+valSize])
|
||||
}
|
||||
|
||||
nameSize = (nameSize + 3) &^ 3
|
||||
valSize = (valSize + 3) &^ 3
|
||||
notesz := uint64(12 + nameSize + valSize)
|
||||
if filesz <= notesz {
|
||||
break
|
||||
}
|
||||
off += notesz
|
||||
align := p.Align
|
||||
alignedOff := (off + align - 1) &^ (align - 1)
|
||||
notesz += alignedOff - off
|
||||
off = alignedOff
|
||||
filesz -= notesz
|
||||
note = note[notesz:]
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find a Go note, use a GNU note if available.
|
||||
// This is what gccgo uses.
|
||||
if gnu != "" {
|
||||
return gnu, nil
|
||||
}
|
||||
|
||||
// No note. Treat as successful but build ID empty.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// The Go build ID is stored at the beginning of the Mach-O __text segment.
|
||||
// The caller has already opened filename, to get f, and read a few kB out, in data.
|
||||
// Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount
|
||||
// of other junk placed in the file ahead of the main text.
|
||||
func readMacho(name string, f *os.File, data []byte) (buildid string, err error) {
|
||||
// If the data we want has already been read, don't worry about Mach-O parsing.
|
||||
// This is both an optimization and a hedge against the Mach-O parsing failing
|
||||
// in the future due to, for example, the name of the __text section changing.
|
||||
if b, err := readRaw(name, data); b != "" && err == nil {
|
||||
return b, err
|
||||
}
|
||||
|
||||
mf, err := macho.NewFile(f)
|
||||
if err != nil {
|
||||
return "", &os.PathError{Path: name, Op: "parse", Err: err}
|
||||
}
|
||||
|
||||
sect := mf.Section("__text")
|
||||
if sect == nil {
|
||||
// Every binary has a __text section. Something is wrong.
|
||||
return "", &os.PathError{Path: name, Op: "parse", Err: fmt.Errorf("cannot find __text section")}
|
||||
}
|
||||
|
||||
// It should be in the first few bytes, but read a lot just in case,
|
||||
// especially given our past problems on OS X with the build ID moving.
|
||||
// There shouldn't be much difference between reading 4kB and 32kB:
|
||||
// the hard part is getting to the data, not transferring it.
|
||||
n := min(sect.Size, uint64(readSize))
|
||||
buf := make([]byte, n)
|
||||
if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return readRaw(name, buf)
|
||||
}
|
||||
28
vendor/honnef.co/go/tools/go/ir/LICENSE
vendored
Normal file
28
vendor/honnef.co/go/tools/go/ir/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.
|
||||
9
vendor/honnef.co/go/tools/go/ir/UPSTREAM
vendored
Normal file
9
vendor/honnef.co/go/tools/go/ir/UPSTREAM
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
This package started as a copy of golang.org/x/tools/go/ssa, imported from an unknown commit in 2016.
|
||||
It has since been heavily modified to match our own needs in an IR.
|
||||
The changes are too many to list here, and it is best to consider this package independent of go/ssa.
|
||||
|
||||
Upstream changes still get applied when they address bugs in portions of code we have inherited.
|
||||
|
||||
The last upstream commit we've looked at was:
|
||||
05409620da166985e94b711ad4103bee40406eee
|
||||
|
||||
205
vendor/honnef.co/go/tools/go/ir/blockopt.go
vendored
Normal file
205
vendor/honnef.co/go/tools/go/ir/blockopt.go
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// Simple block optimizations to simplify the control flow graph.
|
||||
|
||||
// TODO(adonovan): opt: instead of creating several "unreachable" blocks
|
||||
// per function in the Builder, reuse a single one (e.g. at Blocks[1])
|
||||
// to reduce garbage.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// If true, perform sanity checking and show progress at each
|
||||
// successive iteration of optimizeBlocks. Very verbose.
|
||||
const debugBlockOpt = false
|
||||
|
||||
// markReachable sets Index=-1 for all blocks reachable from b.
|
||||
func markReachable(b *BasicBlock) {
|
||||
b.gaps = -1
|
||||
for _, succ := range b.Succs {
|
||||
if succ.gaps == 0 {
|
||||
markReachable(succ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteUnreachableBlocks marks all reachable blocks of f and
|
||||
// eliminates (nils) all others, including possibly cyclic subgraphs.
|
||||
func deleteUnreachableBlocks(f *Function) {
|
||||
const white, black = 0, -1
|
||||
// We borrow b.gaps temporarily as the mark bit.
|
||||
for _, b := range f.Blocks {
|
||||
b.gaps = white
|
||||
}
|
||||
markReachable(f.Blocks[0])
|
||||
// In SSI form, we need the exit to be reachable for correct
|
||||
// post-dominance information. In original form, however, we
|
||||
// cannot unconditionally mark it reachable because we won't
|
||||
// be adding fake edges, and this breaks the calculation of
|
||||
// dominance information.
|
||||
markReachable(f.Exit)
|
||||
for i, b := range f.Blocks {
|
||||
if b.gaps == white {
|
||||
for _, c := range b.Succs {
|
||||
if c.gaps == black {
|
||||
c.removePred(b) // delete white->black edge
|
||||
}
|
||||
}
|
||||
if debugBlockOpt {
|
||||
fmt.Fprintln(os.Stderr, "unreachable", b)
|
||||
}
|
||||
f.Blocks[i] = nil // delete b
|
||||
}
|
||||
}
|
||||
f.removeNilBlocks()
|
||||
}
|
||||
|
||||
// jumpThreading attempts to apply simple jump-threading to block b,
|
||||
// in which a->b->c become a->c if b is just a Jump.
|
||||
// The result is true if the optimization was applied.
|
||||
func jumpThreading(f *Function, b *BasicBlock) bool {
|
||||
if b.Index == 0 {
|
||||
return false // don't apply to entry block
|
||||
}
|
||||
if b.Instrs == nil {
|
||||
return false
|
||||
}
|
||||
for _, pred := range b.Preds {
|
||||
switch pred.Control().(type) {
|
||||
case *ConstantSwitch:
|
||||
// don't optimize away the head blocks of switch statements
|
||||
return false
|
||||
}
|
||||
}
|
||||
if _, ok := b.Instrs[0].(*Jump); !ok {
|
||||
return false // not just a jump
|
||||
}
|
||||
c := b.Succs[0]
|
||||
if c == b {
|
||||
return false // don't apply to degenerate jump-to-self.
|
||||
}
|
||||
if c.hasPhi() {
|
||||
return false // not sound without more effort
|
||||
}
|
||||
for j, a := range b.Preds {
|
||||
a.replaceSucc(b, c)
|
||||
|
||||
// If a now has two edges to c, replace its degenerate If by Jump.
|
||||
if len(a.Succs) == 2 && a.Succs[0] == c && a.Succs[1] == c {
|
||||
jump := new(Jump)
|
||||
jump.setBlock(a)
|
||||
a.Instrs[len(a.Instrs)-1] = jump
|
||||
a.Succs = a.Succs[:1]
|
||||
c.removePred(b)
|
||||
} else {
|
||||
if j == 0 {
|
||||
c.replacePred(b, a)
|
||||
} else {
|
||||
c.Preds = append(c.Preds, a)
|
||||
}
|
||||
}
|
||||
|
||||
if debugBlockOpt {
|
||||
fmt.Fprintln(os.Stderr, "jumpThreading", a, b, c)
|
||||
}
|
||||
}
|
||||
f.Blocks[b.Index] = nil // delete b
|
||||
return true
|
||||
}
|
||||
|
||||
// fuseBlocks attempts to apply the block fusion optimization to block
|
||||
// a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1.
|
||||
// The result is true if the optimization was applied.
|
||||
func fuseBlocks(f *Function, a *BasicBlock) bool {
|
||||
if len(a.Succs) != 1 {
|
||||
return false
|
||||
}
|
||||
if a.Succs[0] == f.Exit {
|
||||
return false
|
||||
}
|
||||
b := a.Succs[0]
|
||||
if len(b.Preds) != 1 {
|
||||
return false
|
||||
}
|
||||
if _, ok := a.Instrs[len(a.Instrs)-1].(*Panic); ok {
|
||||
// panics aren't simple jumps, they have side effects.
|
||||
return false
|
||||
}
|
||||
|
||||
// Degenerate &&/|| ops may result in a straight-line CFG
|
||||
// containing φ-nodes. (Ideally we'd replace such them with
|
||||
// their sole operand but that requires Referrers, built later.)
|
||||
if b.hasPhi() {
|
||||
return false // not sound without further effort
|
||||
}
|
||||
|
||||
// Eliminate jump at end of A, then copy all of B across.
|
||||
a.Instrs = append(a.Instrs[:len(a.Instrs)-1], b.Instrs...)
|
||||
for _, instr := range b.Instrs {
|
||||
instr.setBlock(a)
|
||||
}
|
||||
|
||||
// A inherits B's successors
|
||||
a.Succs = append(a.succs2[:0], b.Succs...)
|
||||
|
||||
// Fix up Preds links of all successors of B.
|
||||
for _, c := range b.Succs {
|
||||
c.replacePred(b, a)
|
||||
}
|
||||
|
||||
if debugBlockOpt {
|
||||
fmt.Fprintln(os.Stderr, "fuseBlocks", a, b)
|
||||
}
|
||||
|
||||
f.Blocks[b.Index] = nil // delete b
|
||||
return true
|
||||
}
|
||||
|
||||
// optimizeBlocks() performs some simple block optimizations on a
|
||||
// completed function: dead block elimination, block fusion, jump
|
||||
// threading.
|
||||
func optimizeBlocks(f *Function) {
|
||||
if debugBlockOpt {
|
||||
f.WriteTo(os.Stderr)
|
||||
mustSanityCheck(f, nil)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
3458
vendor/honnef.co/go/tools/go/ir/builder.go
vendored
Normal file
3458
vendor/honnef.co/go/tools/go/ir/builder.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
297
vendor/honnef.co/go/tools/go/ir/const.go
vendored
Normal file
297
vendor/honnef.co/go/tools/go/ir/const.go
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// This file defines the Const SSA value type.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/typeparams"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// NewConst returns a new constant of the specified value and type.
|
||||
// val must be valid according to the specification of Const.Value.
|
||||
func NewConst(val constant.Value, typ types.Type, source ast.Node) *Const {
|
||||
c := &Const{
|
||||
register: register{
|
||||
typ: typ,
|
||||
},
|
||||
Value: val,
|
||||
}
|
||||
c.setSource(source)
|
||||
return c
|
||||
}
|
||||
|
||||
// intConst returns an 'int' constant that evaluates to i.
|
||||
// (i is an int64 in case the host is narrower than the target.)
|
||||
func intConst(i int64, source ast.Node) *Const {
|
||||
return NewConst(constant.MakeInt64(i), tInt, source)
|
||||
}
|
||||
|
||||
// nilConst returns a nil constant of the specified type, which may
|
||||
// be any reference type, including interfaces.
|
||||
func nilConst(typ types.Type, source ast.Node) *Const {
|
||||
return NewConst(nil, typ, source)
|
||||
}
|
||||
|
||||
// stringConst returns a 'string' constant that evaluates to s.
|
||||
func stringConst(s string, source ast.Node) *Const {
|
||||
return NewConst(constant.MakeString(s), tString, source)
|
||||
}
|
||||
|
||||
// zeroConst returns a new "zero" constant of the specified type.
|
||||
func zeroConst(t types.Type, source ast.Node) Constant {
|
||||
if _, ok := t.Underlying().(*types.Interface); ok && !typeparams.IsTypeParam(t) {
|
||||
// Handle non-generic interface early to simplify following code.
|
||||
return nilConst(t, source)
|
||||
}
|
||||
|
||||
tset := typeutil.NewTypeSet(t)
|
||||
|
||||
switch typ := tset.CoreType().(type) {
|
||||
case *types.Struct:
|
||||
values := make([]Value, typ.NumFields())
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
values[i] = zeroConst(typ.Field(i).Type(), source)
|
||||
}
|
||||
ac := &AggregateConst{
|
||||
register: register{typ: t},
|
||||
Values: values,
|
||||
}
|
||||
ac.setSource(source)
|
||||
return ac
|
||||
case *types.Tuple:
|
||||
values := make([]Value, typ.Len())
|
||||
for i := 0; i < typ.Len(); i++ {
|
||||
values[i] = zeroConst(typ.At(i).Type(), source)
|
||||
}
|
||||
ac := &AggregateConst{
|
||||
register: register{typ: t},
|
||||
Values: values,
|
||||
}
|
||||
ac.setSource(source)
|
||||
return ac
|
||||
}
|
||||
|
||||
isNillable := func(term *types.Term) bool {
|
||||
switch typ := term.Type().Underlying().(type) {
|
||||
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature, *typeutil.Iterator:
|
||||
return true
|
||||
case *types.Basic:
|
||||
switch typ.Kind() {
|
||||
case types.UnsafePointer, types.UntypedNil:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
isInfo := func(info types.BasicInfo) func(*types.Term) bool {
|
||||
return func(term *types.Term) bool {
|
||||
basic, ok := term.Type().Underlying().(*types.Basic)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return (basic.Info() & info) != 0
|
||||
}
|
||||
}
|
||||
|
||||
isArray := func(term *types.Term) bool {
|
||||
_, ok := term.Type().Underlying().(*types.Array)
|
||||
return ok
|
||||
}
|
||||
|
||||
switch {
|
||||
case tset.All(isInfo(types.IsNumeric)):
|
||||
return NewConst(constant.MakeInt64(0), t, source)
|
||||
case tset.All(isInfo(types.IsString)):
|
||||
return NewConst(constant.MakeString(""), t, source)
|
||||
case tset.All(isInfo(types.IsBoolean)):
|
||||
return NewConst(constant.MakeBool(false), t, source)
|
||||
case tset.All(isNillable):
|
||||
return nilConst(t, source)
|
||||
case tset.All(isArray):
|
||||
var k ArrayConst
|
||||
k.setType(t)
|
||||
k.setSource(source)
|
||||
return &k
|
||||
default:
|
||||
var k GenericConst
|
||||
k.setType(t)
|
||||
k.setSource(source)
|
||||
return &k
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Const) RelString(from *types.Package) string {
|
||||
var p string
|
||||
if c.Value == nil {
|
||||
p = "nil"
|
||||
} else if c.Value.Kind() == constant.String {
|
||||
v := constant.StringVal(c.Value)
|
||||
const max = 20
|
||||
// TODO(adonovan): don't cut a rune in half.
|
||||
if len(v) > max {
|
||||
v = v[:max-3] + "..." // abbreviate
|
||||
}
|
||||
p = strconv.Quote(v)
|
||||
} else {
|
||||
p = c.Value.String()
|
||||
}
|
||||
return fmt.Sprintf("Const <%s> {%s}", relType(c.Type(), from), p)
|
||||
}
|
||||
|
||||
func (c *Const) String() string {
|
||||
if c.block == nil {
|
||||
// Constants don't have a block till late in the compilation process. But we want to print consts during
|
||||
// debugging.
|
||||
return c.RelString(nil)
|
||||
}
|
||||
return c.RelString(c.Parent().pkg())
|
||||
}
|
||||
|
||||
func (v *ArrayConst) RelString(pkg *types.Package) string {
|
||||
return fmt.Sprintf("ArrayConst <%s>", relType(v.Type(), pkg))
|
||||
}
|
||||
|
||||
func (v *ArrayConst) String() string {
|
||||
return v.RelString(v.Parent().pkg())
|
||||
}
|
||||
|
||||
func (v *AggregateConst) RelString(pkg *types.Package) string {
|
||||
values := make([]string, len(v.Values))
|
||||
for i, v := range v.Values {
|
||||
if v != nil {
|
||||
values[i] = v.Name()
|
||||
} else {
|
||||
values[i] = "nil"
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("AggregateConst <%s> (%s)", relType(v.Type(), pkg), strings.Join(values, ", "))
|
||||
}
|
||||
|
||||
func (v *AggregateConst) String() string {
|
||||
if v.block == nil {
|
||||
return v.RelString(nil)
|
||||
}
|
||||
return v.RelString(v.Parent().pkg())
|
||||
}
|
||||
|
||||
func (v *GenericConst) RelString(pkg *types.Package) string {
|
||||
return fmt.Sprintf("GenericConst <%s>", relType(v.Type(), pkg))
|
||||
}
|
||||
|
||||
func (v *GenericConst) String() string {
|
||||
return v.RelString(v.Parent().pkg())
|
||||
}
|
||||
|
||||
// IsNil returns true if this constant represents a typed or untyped nil value.
|
||||
func (c *Const) IsNil() bool {
|
||||
return c.Value == nil
|
||||
}
|
||||
|
||||
// Int64 returns the numeric value of this constant truncated to fit
|
||||
// a signed 64-bit integer.
|
||||
func (c *Const) Int64() int64 {
|
||||
switch x := constant.ToInt(c.Value); x.Kind() {
|
||||
case constant.Int:
|
||||
if i, ok := constant.Int64Val(x); ok {
|
||||
return i
|
||||
}
|
||||
return 0
|
||||
case constant.Float:
|
||||
f, _ := constant.Float64Val(x)
|
||||
return int64(f)
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
|
||||
}
|
||||
|
||||
// Uint64 returns the numeric value of this constant truncated to fit
|
||||
// an unsigned 64-bit integer.
|
||||
func (c *Const) Uint64() uint64 {
|
||||
switch x := constant.ToInt(c.Value); x.Kind() {
|
||||
case constant.Int:
|
||||
if u, ok := constant.Uint64Val(x); ok {
|
||||
return u
|
||||
}
|
||||
return 0
|
||||
case constant.Float:
|
||||
f, _ := constant.Float64Val(x)
|
||||
return uint64(f)
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
|
||||
}
|
||||
|
||||
// Float64 returns the numeric value of this constant truncated to fit
|
||||
// a float64.
|
||||
func (c *Const) Float64() float64 {
|
||||
f, _ := constant.Float64Val(c.Value)
|
||||
return f
|
||||
}
|
||||
|
||||
// Complex128 returns the complex value of this constant truncated to
|
||||
// fit a complex128.
|
||||
func (c *Const) Complex128() complex128 {
|
||||
re, _ := constant.Float64Val(constant.Real(c.Value))
|
||||
im, _ := constant.Float64Val(constant.Imag(c.Value))
|
||||
return complex(re, im)
|
||||
}
|
||||
|
||||
func (c *Const) equal(o Constant) bool {
|
||||
// TODO(dh): don't use == for types, this will miss identical pointer types, among others
|
||||
oc, ok := o.(*Const)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return c.typ == oc.typ && c.Value == oc.Value && c.source == oc.source
|
||||
}
|
||||
|
||||
func (c *AggregateConst) equal(o Constant) bool {
|
||||
oc, ok := o.(*AggregateConst)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// TODO(dh): don't use == for types, this will miss identical pointer types, among others
|
||||
if c.typ != oc.typ {
|
||||
return false
|
||||
}
|
||||
if c.source != oc.source {
|
||||
return false
|
||||
}
|
||||
for i, v := range c.Values {
|
||||
if !v.(Constant).equal(oc.Values[i].(Constant)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *ArrayConst) equal(o Constant) bool {
|
||||
oc, ok := o.(*ArrayConst)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// TODO(dh): don't use == for types, this will miss identical pointer types, among others
|
||||
return c.typ == oc.typ && c.source == oc.source
|
||||
}
|
||||
|
||||
func (c *GenericConst) equal(o Constant) bool {
|
||||
oc, ok := o.(*GenericConst)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// TODO(dh): don't use == for types, this will miss identical pointer types, among others
|
||||
return c.typ == oc.typ && c.source == oc.source
|
||||
}
|
||||
300
vendor/honnef.co/go/tools/go/ir/create.go
vendored
Normal file
300
vendor/honnef.co/go/tools/go/ir/create.go
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// This file implements the CREATE phase of IR construction.
|
||||
// See builder.go for explanation.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"go/version"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// measured on the standard library and rounded up to powers of two,
|
||||
// on average there are 8 blocks and 16 instructions per block in a
|
||||
// function.
|
||||
const avgBlocks = 8
|
||||
const avgInstructionsPerBlock = 16
|
||||
|
||||
// NewProgram returns a new IR Program.
|
||||
//
|
||||
// mode controls diagnostics and checking during IR construction.
|
||||
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
||||
prog := &Program{
|
||||
Fset: fset,
|
||||
imported: make(map[string]*Package),
|
||||
packages: make(map[*types.Package]*Package),
|
||||
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) and goversion defines the appropriate
|
||||
// interpretation; they will be used during the build phase.
|
||||
func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion string) {
|
||||
name := obj.Name()
|
||||
switch obj := obj.(type) {
|
||||
case *types.Builtin:
|
||||
if pkg.Pkg != types.Unsafe {
|
||||
panic("unexpected builtin object: " + obj.String())
|
||||
}
|
||||
|
||||
case *types.TypeName:
|
||||
if name != "_" {
|
||||
pkg.Members[name] = &Type{
|
||||
object: obj,
|
||||
pkg: pkg,
|
||||
}
|
||||
}
|
||||
|
||||
case *types.Const:
|
||||
c := &NamedConst{
|
||||
object: obj,
|
||||
Value: NewConst(obj.Val(), obj.Type(), syntax),
|
||||
pkg: pkg,
|
||||
}
|
||||
pkg.values[obj] = c.Value
|
||||
if name != "_" {
|
||||
pkg.Members[name] = c
|
||||
}
|
||||
|
||||
case *types.Var:
|
||||
g := &Global{
|
||||
Pkg: pkg,
|
||||
name: name,
|
||||
object: obj,
|
||||
typ: types.NewPointer(obj.Type()), // address
|
||||
}
|
||||
pkg.values[obj] = g
|
||||
if name != "_" {
|
||||
pkg.Members[name] = g
|
||||
}
|
||||
|
||||
case *types.Func:
|
||||
sig := obj.Type().(*types.Signature)
|
||||
if sig.Recv() == nil && name == "init" {
|
||||
pkg.ninit++
|
||||
name = fmt.Sprintf("init#%d", pkg.ninit)
|
||||
}
|
||||
fn := &Function{
|
||||
name: name,
|
||||
object: obj,
|
||||
Signature: sig,
|
||||
Pkg: pkg,
|
||||
Prog: pkg.Prog,
|
||||
goversion: goversion,
|
||||
}
|
||||
|
||||
fn.source = syntax
|
||||
fn.initHTML(pkg.printFunc)
|
||||
if syntax == nil {
|
||||
fn.Synthetic = SyntheticLoadedFromExportData
|
||||
} else {
|
||||
// Note: we initialize fn.Blocks in
|
||||
// (*builder).buildFunction and not here because Blocks
|
||||
// being nil is used to indicate that building of the
|
||||
// function hasn't started yet.
|
||||
|
||||
fn.functionBody = &functionBody{
|
||||
scratchInstructions: make([]Instruction, avgBlocks*avgInstructionsPerBlock),
|
||||
}
|
||||
}
|
||||
|
||||
pkg.values[obj] = fn
|
||||
pkg.Functions = append(pkg.Functions, fn)
|
||||
if name != "_" && sig.Recv() == nil {
|
||||
pkg.Members[name] = fn // package-level function
|
||||
}
|
||||
|
||||
default: // (incl. *types.Package)
|
||||
panic("unexpected Object type: " + obj.String())
|
||||
}
|
||||
}
|
||||
|
||||
// membersFromDecl populates package pkg with members for each
|
||||
// typechecker object (var, func, const or type) associated with the
|
||||
// specified decl.
|
||||
func membersFromDecl(pkg *Package, decl ast.Decl, goversion string) {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl: // import, const, type or var
|
||||
switch decl.Tok {
|
||||
case token.CONST:
|
||||
for _, spec := range decl.Specs {
|
||||
for _, id := range spec.(*ast.ValueSpec).Names {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], nil, "")
|
||||
}
|
||||
}
|
||||
|
||||
case token.VAR:
|
||||
for _, spec := range decl.Specs {
|
||||
for _, rhs := range spec.(*ast.ValueSpec).Values {
|
||||
pkg.initVersion[rhs] = goversion
|
||||
}
|
||||
for _, id := range spec.(*ast.ValueSpec).Names {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], spec, goversion)
|
||||
}
|
||||
}
|
||||
|
||||
case token.TYPE:
|
||||
for _, spec := range decl.Specs {
|
||||
id := spec.(*ast.TypeSpec).Name
|
||||
memberFromObject(pkg, pkg.info.Defs[id], nil, "")
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
id := decl.Name
|
||||
obj, ok := pkg.info.Defs[id]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("couldn't find object for id %q at %s",
|
||||
id.Name, pkg.Prog.Fset.PositionFor(id.Pos(), false)))
|
||||
}
|
||||
if obj == nil {
|
||||
panic(fmt.Sprintf("found nil object for id %q at %s",
|
||||
id.Name, pkg.Prog.Fset.PositionFor(id.Pos(), false)))
|
||||
}
|
||||
memberFromObject(pkg, obj, decl, goversion)
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePackage constructs and returns an IR Package from the
|
||||
// specified type-checked, error-free file ASTs, and populates its
|
||||
// Members mapping.
|
||||
//
|
||||
// importable determines whether this package should be returned by a
|
||||
// subsequent call to ImportedPackage(pkg.Path()).
|
||||
//
|
||||
// The real work of building IR form for each function is not done
|
||||
// until a subsequent call to Package.Build().
|
||||
func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package {
|
||||
p := &Package{
|
||||
Prog: prog,
|
||||
Members: make(map[string]Member),
|
||||
values: make(map[types.Object]Value),
|
||||
Pkg: pkg,
|
||||
// transient values (CREATE and BUILD phases)
|
||||
info: info,
|
||||
files: files,
|
||||
printFunc: prog.PrintFunc,
|
||||
initVersion: make(map[ast.Expr]string),
|
||||
}
|
||||
|
||||
// Add init() function.
|
||||
p.init = &Function{
|
||||
name: "init",
|
||||
Signature: new(types.Signature),
|
||||
Synthetic: SyntheticPackageInitializer,
|
||||
Pkg: p,
|
||||
Prog: prog,
|
||||
functionBody: new(functionBody),
|
||||
goversion: "", // See Package.build for details.
|
||||
}
|
||||
p.init.initHTML(prog.PrintFunc)
|
||||
p.Members[p.init.name] = p.init
|
||||
p.Functions = append(p.Functions, p.init)
|
||||
|
||||
// CREATE phase.
|
||||
// Allocate all package members: vars, funcs, consts and types.
|
||||
if len(files) > 0 {
|
||||
// Go source package.
|
||||
for _, file := range files {
|
||||
goversion := version.Lang(p.info.FileVersions[file])
|
||||
for _, decl := range file.Decls {
|
||||
membersFromDecl(p, decl, goversion)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// GC-compiled binary package (or "unsafe")
|
||||
// No code.
|
||||
// No position information.
|
||||
scope := p.Pkg.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
obj := scope.Lookup(name)
|
||||
memberFromObject(p, obj, nil, "")
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
if named, ok := obj.Type().(*types.Named); ok {
|
||||
for i, n := 0, named.NumMethods(); i < n; i++ {
|
||||
memberFromObject(p, named.Method(i), nil, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 Package whose PkgPath
|
||||
// is path, or nil if no such Package has been created.
|
||||
//
|
||||
// A parameter to CreatePackage determines whether a package should be
|
||||
// considered importable. For example, no import declaration can resolve
|
||||
// to the ad-hoc main package created by 'go build foo.go'.
|
||||
//
|
||||
// TODO(adonovan): rethink this function and the "importable" concept;
|
||||
// most packages are importable. This function assumes that all
|
||||
// types.Package.Path values are unique within the ir.Program, which is
|
||||
// false---yet this function remains very convenient.
|
||||
// Clients should use (*Program).Package instead where possible.
|
||||
// IR doesn't really need a string-keyed map of packages.
|
||||
func (prog *Program) ImportedPackage(path string) *Package {
|
||||
return prog.imported[path]
|
||||
}
|
||||
|
||||
func (prog *Program) SetNoReturn(fn func(*types.Func) bool) {
|
||||
prog.noReturn = fn
|
||||
}
|
||||
131
vendor/honnef.co/go/tools/go/ir/doc.go
vendored
Normal file
131
vendor/honnef.co/go/tools/go/ir/doc.go
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ir defines a representation of the elements of Go programs
|
||||
// (packages, types, functions, variables and constants) using a
|
||||
// static single-information (SSI) form intermediate representation
|
||||
// (IR) for the bodies of functions.
|
||||
//
|
||||
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
|
||||
//
|
||||
// For an introduction to SSA form, upon which SSI builds, see
|
||||
// https://en.wikipedia.org/wiki/Static_single_assignment_form.
|
||||
// This page provides a broader reading list:
|
||||
// https://www.dcs.gla.ac.uk/~jsinger/ssa.html.
|
||||
//
|
||||
// For an introduction to SSI form, see The static single information
|
||||
// form by C. Scott Ananian.
|
||||
//
|
||||
// The level of abstraction of the IR form is intentionally close to
|
||||
// the source language to facilitate construction of source analysis
|
||||
// tools. It is not intended for machine code generation.
|
||||
//
|
||||
// The simplest way to create the IR of a package is
|
||||
// to load typed syntax trees using golang.org/x/tools/go/packages, then
|
||||
// invoke the irutil.Packages helper function. See ExampleLoadPackages
|
||||
// and ExampleWholeProgram for examples.
|
||||
// The resulting ir.Program contains all the packages and their
|
||||
// members, but IR code is not created for function bodies until a
|
||||
// subsequent call to (*Package).Build or (*Program).Build.
|
||||
//
|
||||
// The builder initially builds a naive IR form in which all local
|
||||
// variables are addresses of stack locations with explicit loads and
|
||||
// stores. Registerization of eligible locals and φ-node insertion
|
||||
// using dominance and dataflow are then performed as a second pass
|
||||
// called "lifting" to improve the accuracy and performance of
|
||||
// subsequent analyses; this pass can be skipped by setting the
|
||||
// NaiveForm builder flag.
|
||||
//
|
||||
// The primary interfaces of this package are:
|
||||
//
|
||||
// - Member: a named member of a Go package.
|
||||
// - Value: an expression that yields a value.
|
||||
// - Instruction: a statement that consumes values and performs computation.
|
||||
// - Node: a Value or Instruction (emphasizing its membership in the IR value graph)
|
||||
//
|
||||
// A computation that yields a result implements both the Value and
|
||||
// Instruction interfaces. The following table shows for each
|
||||
// concrete type which of these interfaces it implements.
|
||||
//
|
||||
// Value? Instruction? Member?
|
||||
// *Alloc ✔ ✔
|
||||
// *BinOp ✔ ✔
|
||||
// *BlankStore ✔
|
||||
// *Builtin ✔
|
||||
// *Call ✔ ✔
|
||||
// *ChangeInterface ✔ ✔
|
||||
// *ChangeType ✔ ✔
|
||||
// *Const ✔ ✔
|
||||
// *Convert ✔ ✔
|
||||
// *DebugRef ✔
|
||||
// *Defer ✔ ✔
|
||||
// *Extract ✔ ✔
|
||||
// *Field ✔ ✔
|
||||
// *FieldAddr ✔ ✔
|
||||
// *FreeVar ✔
|
||||
// *Function ✔ ✔ (func)
|
||||
// *Global ✔ ✔ (var)
|
||||
// *Go ✔ ✔
|
||||
// *If ✔
|
||||
// *Index ✔ ✔
|
||||
// *IndexAddr ✔ ✔
|
||||
// *Jump ✔
|
||||
// *Load ✔ ✔
|
||||
// *MakeChan ✔ ✔
|
||||
// *MakeClosure ✔ ✔
|
||||
// *MakeInterface ✔ ✔
|
||||
// *MakeMap ✔ ✔
|
||||
// *MakeSlice ✔ ✔
|
||||
// *MapLookup ✔ ✔
|
||||
// *MapUpdate ✔ ✔
|
||||
// *MultiConvert ✔ ✔
|
||||
// *NamedConst ✔ (const)
|
||||
// *Next ✔ ✔
|
||||
// *Panic ✔
|
||||
// *Parameter ✔ ✔
|
||||
// *Phi ✔ ✔
|
||||
// *Range ✔ ✔
|
||||
// *Recv ✔ ✔
|
||||
// *Return ✔
|
||||
// *RunDefers ✔
|
||||
// *Select ✔ ✔
|
||||
// *Send ✔ ✔
|
||||
// *Sigma ✔ ✔
|
||||
// *Slice ✔ ✔
|
||||
// *SliceToArrayPointer ✔ ✔
|
||||
// *SliceToArray ✔ ✔
|
||||
// *Store ✔ ✔
|
||||
// *StringLookup ✔ ✔
|
||||
// *Type ✔ (type)
|
||||
// *TypeAssert ✔ ✔
|
||||
// *UnOp ✔ ✔
|
||||
// *Unreachable ✔
|
||||
//
|
||||
// Other key types in this package include: Program, Package, Function
|
||||
// and BasicBlock.
|
||||
//
|
||||
// The program representation constructed by this package is fully
|
||||
// resolved internally, i.e. it does not rely on the names of Values,
|
||||
// Packages, Functions, Types or BasicBlocks for the correct
|
||||
// interpretation of the program. Only the identities of objects and
|
||||
// the topology of the IR and type graphs are semantically
|
||||
// significant. (There is one exception: 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 ir/irutil 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,
|
||||
// ir.Values/Instructions.
|
||||
package ir
|
||||
466
vendor/honnef.co/go/tools/go/ir/dom.go
vendored
Normal file
466
vendor/honnef.co/go/tools/go/ir/dom.go
vendored
Normal file
@@ -0,0 +1,466 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// This file defines algorithms related to dominance.
|
||||
|
||||
// Dominator tree construction ----------------------------------------
|
||||
//
|
||||
// We use the algorithm described in Lengauer & Tarjan. 1979. A fast
|
||||
// algorithm for finding dominators in a flowgraph.
|
||||
// https://doi.acm.org/10.1145/357062.357071
|
||||
//
|
||||
// We also apply the optimizations to SLT described in Georgiadis et
|
||||
// al, Finding Dominators in Practice, JGAA 2006,
|
||||
// https://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf
|
||||
// to avoid the need for buckets of size > 1.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Idom returns the block that immediately dominates b:
|
||||
// its parent in the dominator tree, if any.
|
||||
// The entry node (b.Index==0) does not 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)
|
||||
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
|
||||
}
|
||||
|
||||
// buildDomTree computes the dominator tree of f using the LT algorithm.
|
||||
// Precondition: all blocks are reachable (e.g. optimizeBlocks has been run).
|
||||
func buildDomTree(fn *Function) {
|
||||
// The step numbers refer to the original LT paper; the
|
||||
// reordering is due to Georgiadis.
|
||||
|
||||
// Clear any previous domInfo.
|
||||
for _, b := range fn.Blocks {
|
||||
b.dom = domInfo{}
|
||||
}
|
||||
|
||||
idoms := make([]*BasicBlock, len(fn.Blocks))
|
||||
|
||||
order := make([]*BasicBlock, 0, len(fn.Blocks))
|
||||
seen := fn.blockset(0)
|
||||
var dfs func(b *BasicBlock)
|
||||
dfs = func(b *BasicBlock) {
|
||||
if !seen.Add(b) {
|
||||
return
|
||||
}
|
||||
for _, succ := range b.Succs {
|
||||
dfs(succ)
|
||||
}
|
||||
if fn.fakeExits.Has(b) {
|
||||
dfs(fn.Exit)
|
||||
}
|
||||
order = append(order, b)
|
||||
b.post = len(order) - 1
|
||||
}
|
||||
dfs(fn.Blocks[0])
|
||||
|
||||
for i := 0; i < len(order)/2; i++ {
|
||||
o := len(order) - i - 1
|
||||
order[i], order[o] = order[o], order[i]
|
||||
}
|
||||
|
||||
idoms[fn.Blocks[0].Index] = fn.Blocks[0]
|
||||
changed := true
|
||||
for changed {
|
||||
changed = false
|
||||
// iterate over all nodes in reverse postorder, except for the
|
||||
// entry node
|
||||
for _, b := range order[1:] {
|
||||
var newIdom *BasicBlock
|
||||
do := func(p *BasicBlock) {
|
||||
if idoms[p.Index] == nil {
|
||||
return
|
||||
}
|
||||
if newIdom == nil {
|
||||
newIdom = p
|
||||
} else {
|
||||
finger1 := p
|
||||
finger2 := newIdom
|
||||
for finger1 != finger2 {
|
||||
for finger1.post < finger2.post {
|
||||
finger1 = idoms[finger1.Index]
|
||||
}
|
||||
for finger2.post < finger1.post {
|
||||
finger2 = idoms[finger2.Index]
|
||||
}
|
||||
}
|
||||
newIdom = finger1
|
||||
}
|
||||
}
|
||||
for _, p := range b.Preds {
|
||||
do(p)
|
||||
}
|
||||
if b == fn.Exit {
|
||||
for _, p := range fn.Blocks {
|
||||
if fn.fakeExits.Has(p) {
|
||||
do(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if idoms[b.Index] != newIdom {
|
||||
idoms[b.Index] = newIdom
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, b := range idoms {
|
||||
fn.Blocks[i].dom.idom = b
|
||||
if b == nil {
|
||||
// malformed CFG
|
||||
continue
|
||||
}
|
||||
if i == b.Index {
|
||||
continue
|
||||
}
|
||||
b.dom.children = append(b.dom.children, fn.Blocks[i])
|
||||
}
|
||||
|
||||
numberDomTree(fn.Blocks[0], 0, 0)
|
||||
|
||||
// printDomTreeDot(os.Stderr, fn) // debugging
|
||||
// printDomTreeText(os.Stderr, root, 0) // debugging
|
||||
|
||||
if fn.Prog.mode&SanityCheckFunctions != 0 {
|
||||
sanityCheckDomTree(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// buildPostDomTree is like buildDomTree, but builds the post-dominator tree instead.
|
||||
func buildPostDomTree(fn *Function) {
|
||||
// The step numbers refer to the original LT paper; the
|
||||
// reordering is due to Georgiadis.
|
||||
|
||||
// Clear any previous domInfo.
|
||||
for _, b := range fn.Blocks {
|
||||
b.pdom = domInfo{}
|
||||
}
|
||||
|
||||
idoms := make([]*BasicBlock, len(fn.Blocks))
|
||||
|
||||
order := make([]*BasicBlock, 0, len(fn.Blocks))
|
||||
seen := fn.blockset(0)
|
||||
var dfs func(b *BasicBlock)
|
||||
dfs = func(b *BasicBlock) {
|
||||
if !seen.Add(b) {
|
||||
return
|
||||
}
|
||||
for _, pred := range b.Preds {
|
||||
dfs(pred)
|
||||
}
|
||||
if b == fn.Exit {
|
||||
for _, p := range fn.Blocks {
|
||||
if fn.fakeExits.Has(p) {
|
||||
dfs(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
order = append(order, b)
|
||||
b.post = len(order) - 1
|
||||
}
|
||||
dfs(fn.Exit)
|
||||
|
||||
for i := 0; i < len(order)/2; i++ {
|
||||
o := len(order) - i - 1
|
||||
order[i], order[o] = order[o], order[i]
|
||||
}
|
||||
|
||||
idoms[fn.Exit.Index] = fn.Exit
|
||||
changed := true
|
||||
for changed {
|
||||
changed = false
|
||||
// iterate over all nodes in reverse postorder, except for the
|
||||
// exit node
|
||||
for _, b := range order[1:] {
|
||||
var newIdom *BasicBlock
|
||||
do := func(p *BasicBlock) {
|
||||
if idoms[p.Index] == nil {
|
||||
return
|
||||
}
|
||||
if newIdom == nil {
|
||||
newIdom = p
|
||||
} else {
|
||||
finger1 := p
|
||||
finger2 := newIdom
|
||||
for finger1 != finger2 {
|
||||
for finger1.post < finger2.post {
|
||||
finger1 = idoms[finger1.Index]
|
||||
}
|
||||
for finger2.post < finger1.post {
|
||||
finger2 = idoms[finger2.Index]
|
||||
}
|
||||
}
|
||||
newIdom = finger1
|
||||
}
|
||||
}
|
||||
for _, p := range b.Succs {
|
||||
do(p)
|
||||
}
|
||||
if fn.fakeExits.Has(b) {
|
||||
do(fn.Exit)
|
||||
}
|
||||
|
||||
if idoms[b.Index] != newIdom {
|
||||
idoms[b.Index] = newIdom
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, b := range idoms {
|
||||
fn.Blocks[i].pdom.idom = b
|
||||
if b == nil {
|
||||
// malformed CFG
|
||||
continue
|
||||
}
|
||||
if i == b.Index {
|
||||
continue
|
||||
}
|
||||
b.pdom.children = append(b.pdom.children, fn.Blocks[i])
|
||||
}
|
||||
|
||||
numberPostDomTree(fn.Exit, 0, 0)
|
||||
|
||||
// printPostDomTreeDot(os.Stderr, fn) // debugging
|
||||
// printPostDomTreeText(os.Stderr, fn.Exit, 0) // debugging
|
||||
|
||||
if fn.Prog.mode&SanityCheckFunctions != 0 { // XXX
|
||||
sanityCheckDomTree(fn) // XXX
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// numberPostDomTree sets the pre- and post-order numbers of a depth-first
|
||||
// traversal of the post-dominator tree rooted at v. These are used to
|
||||
// answer post-dominance queries in constant time.
|
||||
func numberPostDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
|
||||
v.pdom.pre = pre
|
||||
pre++
|
||||
for _, child := range v.pdom.children {
|
||||
pre, post = numberPostDomTree(child, pre, post)
|
||||
}
|
||||
v.pdom.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 := range f.Blocks {
|
||||
if i == 0 {
|
||||
// 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 {
|
||||
continue
|
||||
}
|
||||
// Compute intersection across predecessors.
|
||||
var x big.Int
|
||||
x.Set(&all)
|
||||
for _, pred := range b.Preds {
|
||||
x.And(&x, &D[pred.Index])
|
||||
}
|
||||
if b == f.Exit {
|
||||
for _, p := range f.Blocks {
|
||||
if f.fakeExits.Has(p) {
|
||||
x.And(&x, &D[p.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).
|
||||
ok := true
|
||||
for i := range n {
|
||||
for j := range n {
|
||||
b, c := f.Blocks[i], f.Blocks[j]
|
||||
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.
|
||||
//
|
||||
//lint:ignore U1000 used during debugging
|
||||
func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) {
|
||||
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
|
||||
for _, child := range v.dom.children {
|
||||
printDomTreeText(buf, child, indent+1)
|
||||
}
|
||||
}
|
||||
|
||||
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
|
||||
// (.dot) format.
|
||||
//
|
||||
//lint:ignore U1000 used during debugging
|
||||
func printDomTreeDot(buf io.Writer, f *Function) {
|
||||
fmt.Fprintln(buf, "//", f)
|
||||
fmt.Fprintln(buf, "digraph domtree {")
|
||||
for i, b := range f.Blocks {
|
||||
v := b.dom
|
||||
fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
|
||||
// TODO(adonovan): improve appearance of edges
|
||||
// belonging to both dominator tree and CFG.
|
||||
|
||||
// Dominator tree edge.
|
||||
if i != 0 {
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre)
|
||||
}
|
||||
// CFG edges.
|
||||
for _, pred := range b.Preds {
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre)
|
||||
}
|
||||
|
||||
if f.fakeExits.Has(b) {
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0,color=red];\n", b.dom.pre, f.Exit.dom.pre)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(buf, "}")
|
||||
}
|
||||
|
||||
// printDomTree prints the dominator tree as text, using indentation.
|
||||
//
|
||||
//lint:ignore U1000 used during debugging
|
||||
func printPostDomTreeText(buf io.Writer, v *BasicBlock, indent int) {
|
||||
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
|
||||
for _, child := range v.pdom.children {
|
||||
printPostDomTreeText(buf, child, indent+1)
|
||||
}
|
||||
}
|
||||
|
||||
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
|
||||
// (.dot) format.
|
||||
//
|
||||
//lint:ignore U1000 used during debugging
|
||||
func printPostDomTreeDot(buf io.Writer, f *Function) {
|
||||
fmt.Fprintln(buf, "//", f)
|
||||
fmt.Fprintln(buf, "digraph pdomtree {")
|
||||
for _, b := range f.Blocks {
|
||||
v := b.pdom
|
||||
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 b != f.Exit {
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.pdom.pre, v.pre)
|
||||
}
|
||||
// CFG edges.
|
||||
for _, pred := range b.Preds {
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.pdom.pre, v.pre)
|
||||
}
|
||||
|
||||
if f.fakeExits.Has(b) {
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0,color=red];\n", b.dom.pre, f.Exit.dom.pre)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(buf, "}")
|
||||
}
|
||||
658
vendor/honnef.co/go/tools/go/ir/emit.go
vendored
Normal file
658
vendor/honnef.co/go/tools/go/ir/emit.go
vendored
Normal file
@@ -0,0 +1,658 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// Helpers for emitting IR instructions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
|
||||
"golang.org/x/exp/typeparams"
|
||||
)
|
||||
|
||||
// emitAlloc emits to f a new Alloc instruction allocating a variable
|
||||
// of type typ.
|
||||
//
|
||||
// The caller must set Alloc.Heap=true (for a heap-allocated variable)
|
||||
// or add the Alloc to f.Locals (for a frame-allocated variable).
|
||||
//
|
||||
// During building, a variable in f.Locals may have its Heap flag
|
||||
// set when it is discovered that its address is taken.
|
||||
// These Allocs are removed from f.Locals at the end.
|
||||
//
|
||||
// The builder should generally call one of the emit{New,Local,LocalVar} wrappers instead.
|
||||
func emitAlloc(f *Function, typ types.Type, source ast.Node, comment string) *Alloc {
|
||||
v := &Alloc{}
|
||||
v.comment = comment
|
||||
v.setType(types.NewPointer(typ))
|
||||
f.emit(v, source)
|
||||
return v
|
||||
}
|
||||
|
||||
// emitNew emits to f a new Alloc instruction heap-allocating a
|
||||
// variable of type typ.
|
||||
func emitNew(f *Function, typ types.Type, source ast.Node, comment string) *Alloc {
|
||||
alloc := emitAlloc(f, typ, source, comment)
|
||||
alloc.Heap = true
|
||||
return alloc
|
||||
}
|
||||
|
||||
// emitLocal creates a local var for (t, source, comment) and
|
||||
// emits an Alloc instruction for it.
|
||||
//
|
||||
// (Use this function or emitNew for synthetic variables;
|
||||
// for source-level variables, use emitLocalVar.)
|
||||
func emitLocal(f *Function, t types.Type, source ast.Node, comment string) *Alloc {
|
||||
local := emitAlloc(f, t, source, comment)
|
||||
f.Locals = append(f.Locals, local)
|
||||
return local
|
||||
}
|
||||
|
||||
// emitLocalVar creates a local var for v and emits an Alloc instruction for it.
|
||||
// Subsequent calls to f.lookup(v) return it.
|
||||
func emitLocalVar(f *Function, v *types.Var, source ast.Node) *Alloc {
|
||||
alloc := emitLocal(f, v.Type(), source, v.Name())
|
||||
f.vars[v] = alloc
|
||||
return alloc
|
||||
}
|
||||
|
||||
// emitLoad emits to f an instruction to load the address addr into a
|
||||
// new temporary, and returns the value so defined.
|
||||
func emitLoad(f *Function, addr Value, source ast.Node) *Load {
|
||||
v := &Load{X: addr}
|
||||
v.setType(deref(addr.Type()))
|
||||
f.emit(v, source)
|
||||
return v
|
||||
}
|
||||
|
||||
func emitRecv(f *Function, ch Value, commaOk bool, typ types.Type, source ast.Node) Value {
|
||||
recv := &Recv{
|
||||
Chan: ch,
|
||||
CommaOk: commaOk,
|
||||
}
|
||||
recv.setType(typ)
|
||||
return f.emit(recv, source)
|
||||
}
|
||||
|
||||
// emitDebugRef emits to f a DebugRef pseudo-instruction associating
|
||||
// expression e with value v.
|
||||
func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) {
|
||||
ref := makeDebugRef(f, e, v, isAddr)
|
||||
if ref == nil {
|
||||
return
|
||||
}
|
||||
f.emit(ref, nil)
|
||||
}
|
||||
|
||||
func makeDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) *DebugRef {
|
||||
if !f.debugInfo() {
|
||||
return nil // debugging not enabled
|
||||
}
|
||||
if v == nil || e == nil {
|
||||
panic("nil")
|
||||
}
|
||||
var obj types.Object
|
||||
e = unparen(e)
|
||||
if id, ok := e.(*ast.Ident); ok {
|
||||
if isBlankIdent(id) {
|
||||
return nil
|
||||
}
|
||||
obj = f.Pkg.objectOf(id)
|
||||
switch obj.(type) {
|
||||
case *types.Nil, *types.Const, *types.Builtin:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return &DebugRef{
|
||||
X: v,
|
||||
Expr: e,
|
||||
IsAddr: isAddr,
|
||||
object: obj,
|
||||
}
|
||||
}
|
||||
|
||||
// emitArith emits to f code to compute the binary operation op(x, y)
|
||||
// where op is an eager shift, logical or arithmetic operation.
|
||||
// (Use emitCompare() for comparisons and Builder.logicalBinop() for
|
||||
// non-eager operations.)
|
||||
func emitArith(f *Function, op token.Token, x, y Value, t types.Type, source ast.Node) Value {
|
||||
switch op {
|
||||
case token.SHL, token.SHR:
|
||||
x = emitConv(f, x, t, source)
|
||||
// y may be signed or an 'untyped' constant.
|
||||
// There is a runtime panic if y is signed and <0. Instead of inserting a check for y<0
|
||||
// and converting to an unsigned value (like the compiler) leave y as is.
|
||||
if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
|
||||
// Untyped conversion:
|
||||
// Spec https://go.dev/ref/spec#Operators:
|
||||
// The right operand in a shift expression must have integer type or be an untyped constant
|
||||
// representable by a value of type uint.
|
||||
y = emitConv(f, y, types.Typ[types.Uint], source)
|
||||
}
|
||||
|
||||
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
|
||||
x = emitConv(f, x, t, source)
|
||||
y = emitConv(f, y, t, source)
|
||||
|
||||
default:
|
||||
panic("illegal op in emitArith: " + op.String())
|
||||
|
||||
}
|
||||
v := &BinOp{
|
||||
Op: op,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
v.setType(t)
|
||||
return f.emit(v, source)
|
||||
}
|
||||
|
||||
// emitCompare emits to f code compute the boolean result of
|
||||
// comparison 'x op y'.
|
||||
func emitCompare(f *Function, op token.Token, x, y Value, source ast.Node) Value {
|
||||
xt := x.Type().Underlying()
|
||||
yt := y.Type().Underlying()
|
||||
|
||||
// Special case to optimise a tagless SwitchStmt so that
|
||||
// these are equivalent
|
||||
// switch { case e: ...}
|
||||
// switch true { case e: ... }
|
||||
// if e==true { ... }
|
||||
// even in the case when e's type is an interface.
|
||||
// TODO(adonovan): opt: generalise to x==true, false!=y, etc.
|
||||
if x, ok := x.(*Const); ok && op == token.EQL && x.Value != nil && x.Value.Kind() == constant.Bool && constant.BoolVal(x.Value) {
|
||||
if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 {
|
||||
return y
|
||||
}
|
||||
}
|
||||
|
||||
if types.Identical(xt, yt) {
|
||||
// no conversion necessary
|
||||
} else if _, ok := xt.(*types.Interface); ok && !typeparams.IsTypeParam(x.Type()) {
|
||||
y = emitConv(f, y, x.Type(), source)
|
||||
} else if _, ok := yt.(*types.Interface); ok && !typeparams.IsTypeParam(y.Type()) {
|
||||
x = emitConv(f, x, y.Type(), source)
|
||||
} else if _, ok := x.(*Const); ok {
|
||||
x = emitConv(f, x, y.Type(), source)
|
||||
} else if _, ok := y.(*Const); ok {
|
||||
y = emitConv(f, y, x.Type(), source)
|
||||
//lint:ignore SA9003 no-op
|
||||
} else {
|
||||
// other cases, e.g. channels. No-op.
|
||||
}
|
||||
|
||||
v := &BinOp{
|
||||
Op: op,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
v.setType(tBool)
|
||||
return f.emit(v, source)
|
||||
}
|
||||
|
||||
// isValuePreserving returns true if a conversion from ut_src to
|
||||
// ut_dst is value-preserving, i.e. just a change of type.
|
||||
// Precondition: neither argument is a named type.
|
||||
func isValuePreserving(ut_src, ut_dst types.Type) bool {
|
||||
// Identical underlying types?
|
||||
if types.IdenticalIgnoreTags(ut_dst, ut_src) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch ut_dst.(type) {
|
||||
case *types.Chan:
|
||||
// Conversion between channel types?
|
||||
_, ok := ut_src.(*types.Chan)
|
||||
return ok
|
||||
|
||||
case *types.Pointer:
|
||||
// Conversion between pointers with identical base types?
|
||||
_, ok := ut_src.(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// emitConv emits to f code to convert Value val to exactly type typ,
|
||||
// and returns the converted value. Implicit conversions are required
|
||||
// by language assignability rules in assignments, parameter passing,
|
||||
// etc.
|
||||
func emitConv(f *Function, val Value, t_dst types.Type, source ast.Node) Value {
|
||||
t_src := val.Type()
|
||||
|
||||
// Identical types? Conversion is a no-op.
|
||||
if types.Identical(t_src, t_dst) {
|
||||
return val
|
||||
}
|
||||
|
||||
ut_dst := t_dst.Underlying()
|
||||
ut_src := t_src.Underlying()
|
||||
|
||||
// Conversion to, or construction of a value of, an interface type?
|
||||
if isNonTypeParamInterface(t_dst) {
|
||||
// Interface name change?
|
||||
if isValuePreserving(ut_src, ut_dst) {
|
||||
c := &ChangeType{X: val}
|
||||
c.setType(t_dst)
|
||||
return f.emit(c, source)
|
||||
}
|
||||
|
||||
// Assignment from one interface type to another?
|
||||
if isNonTypeParamInterface(t_src) {
|
||||
c := &ChangeInterface{X: val}
|
||||
c.setType(t_dst)
|
||||
return f.emit(c, source)
|
||||
}
|
||||
|
||||
// Untyped nil constant? Return interface-typed nil constant.
|
||||
if ut_src == tUntypedNil {
|
||||
return emitConst(f, zeroConst(t_dst, source))
|
||||
}
|
||||
|
||||
// Convert (non-nil) "untyped" literals to their default type.
|
||||
if t, ok := ut_src.(*types.Basic); ok && t.Info()&types.IsUntyped != 0 {
|
||||
val = emitConv(f, val, types.Default(ut_src), source)
|
||||
}
|
||||
|
||||
f.Pkg.Prog.needMethodsOf(val.Type())
|
||||
mi := &MakeInterface{X: val}
|
||||
mi.setType(t_dst)
|
||||
return f.emit(mi, source)
|
||||
}
|
||||
|
||||
// In the common case, the typesets of src and dst are singletons
|
||||
// and we emit an appropriate conversion. But if either contains
|
||||
// a type parameter, the conversion may represent a cross product,
|
||||
// in which case which we emit a MultiConvert.
|
||||
tset_dst := typeutil.NewTypeSet(ut_dst)
|
||||
tset_src := typeutil.NewTypeSet(ut_src)
|
||||
|
||||
// conversionCase describes an instruction pattern that may be emitted to
|
||||
// model d <- s for d in dst_terms and s in src_terms.
|
||||
// Multiple conversions can match the same pattern.
|
||||
type conversionCase uint8
|
||||
const (
|
||||
changeType conversionCase = 1 << iota
|
||||
sliceToArray
|
||||
sliceToArrayPtr
|
||||
sliceTo0Array
|
||||
sliceTo0ArrayPtr
|
||||
convert
|
||||
)
|
||||
|
||||
classify := func(s, d types.Type) conversionCase {
|
||||
// Just a change of type, but not value or representation?
|
||||
if isValuePreserving(s, d) {
|
||||
return changeType
|
||||
}
|
||||
|
||||
// Conversion from slice to array or slice to array pointer?
|
||||
if slice, ok := s.(*types.Slice); ok {
|
||||
var arr *types.Array
|
||||
var ptr bool
|
||||
// Conversion from slice to array pointer?
|
||||
switch d := d.(type) {
|
||||
case *types.Array:
|
||||
arr = d
|
||||
case *types.Pointer:
|
||||
arr, _ = d.Elem().Underlying().(*types.Array)
|
||||
ptr = true
|
||||
}
|
||||
if arr != nil && types.Identical(slice.Elem(), arr.Elem()) {
|
||||
if arr.Len() == 0 {
|
||||
if ptr {
|
||||
return sliceTo0ArrayPtr
|
||||
} else {
|
||||
return sliceTo0Array
|
||||
}
|
||||
}
|
||||
if ptr {
|
||||
return sliceToArrayPtr
|
||||
} else {
|
||||
return sliceToArray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The only remaining case in well-typed code is a representation-
|
||||
// changing conversion of basic types (possibly with []byte/[]rune).
|
||||
if !isBasic(s) && !isBasic(d) {
|
||||
panic(fmt.Sprintf("in %s: cannot convert term %s (%s [within %s]) to type %s [within %s]", f, val, val.Type(), s, t_dst, d))
|
||||
}
|
||||
return convert
|
||||
}
|
||||
|
||||
var classifications conversionCase
|
||||
for _, s := range tset_src.Terms {
|
||||
us := s.Type().Underlying()
|
||||
for _, d := range tset_dst.Terms {
|
||||
ud := d.Type().Underlying()
|
||||
classifications |= classify(us, ud)
|
||||
}
|
||||
}
|
||||
if classifications == 0 {
|
||||
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), t_dst))
|
||||
}
|
||||
|
||||
// Conversion of a compile-time constant value?
|
||||
if c, ok := val.(*Const); ok {
|
||||
// Conversion to a basic type?
|
||||
if isBasic(ut_dst) {
|
||||
// Conversion of a compile-time constant to
|
||||
// another constant type results in a new
|
||||
// constant of the destination type and
|
||||
// (initially) the same abstract value.
|
||||
// We don't truncate the value yet.
|
||||
return emitConst(f, NewConst(c.Value, t_dst, source))
|
||||
}
|
||||
// Can we always convert from zero value without panicking?
|
||||
const mayPanic = sliceToArray | sliceToArrayPtr
|
||||
if c.Value == nil && classifications&mayPanic == 0 {
|
||||
return emitConst(f, NewConst(nil, t_dst, source))
|
||||
}
|
||||
|
||||
// We're converting from constant to non-constant type,
|
||||
// e.g. string -> []byte/[]rune.
|
||||
}
|
||||
|
||||
switch classifications {
|
||||
case changeType: // representation-preserving change
|
||||
c := &ChangeType{X: val}
|
||||
c.setType(t_dst)
|
||||
return f.emit(c, source)
|
||||
|
||||
case sliceToArrayPtr, sliceTo0ArrayPtr: // slice to array pointer
|
||||
c := &SliceToArrayPointer{X: val}
|
||||
c.setType(t_dst)
|
||||
return f.emit(c, source)
|
||||
|
||||
case sliceToArray: // slice to arrays (not zero-length)
|
||||
p := &SliceToArray{X: val}
|
||||
p.setType(t_dst)
|
||||
return f.emit(p, source)
|
||||
|
||||
case sliceTo0Array: // slice to zero-length arrays (constant)
|
||||
return emitConst(f, zeroConst(t_dst, source))
|
||||
|
||||
case convert: // representation-changing conversion
|
||||
c := &Convert{X: val}
|
||||
c.setType(t_dst)
|
||||
return f.emit(c, source)
|
||||
|
||||
default: // multiple conversion
|
||||
c := &MultiConvert{X: val, from: tset_src, to: tset_dst}
|
||||
c.setType(t_dst)
|
||||
return f.emit(c, source)
|
||||
}
|
||||
}
|
||||
|
||||
// emitStore emits to f an instruction to store value val at location
|
||||
// addr, applying implicit conversions as required by assignability rules.
|
||||
func emitStore(f *Function, addr, val Value, source ast.Node) *Store {
|
||||
s := &Store{
|
||||
Addr: addr,
|
||||
Val: emitConv(f, val, deref(addr.Type()), source),
|
||||
}
|
||||
f.emit(s, source)
|
||||
return s
|
||||
}
|
||||
|
||||
// emitJump emits to f a jump to target, and updates the control-flow graph.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
func emitJump(f *Function, target *BasicBlock, source ast.Node) *Jump {
|
||||
b := f.currentBlock
|
||||
j := new(Jump)
|
||||
b.emit(j, source)
|
||||
addEdge(b, target)
|
||||
f.currentBlock = nil
|
||||
return j
|
||||
}
|
||||
|
||||
// emitIf emits to f a conditional jump to tblock or fblock based on
|
||||
// cond, and updates the control-flow graph.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock, source ast.Node) *If {
|
||||
b := f.currentBlock
|
||||
stmt := &If{Cond: cond}
|
||||
b.emit(stmt, source)
|
||||
addEdge(b, tblock)
|
||||
addEdge(b, fblock)
|
||||
f.currentBlock = nil
|
||||
return stmt
|
||||
}
|
||||
|
||||
// emitExtract emits to f an instruction to extract the index'th
|
||||
// component of tuple. It returns the extracted value.
|
||||
func emitExtract(f *Function, tuple Value, index int, source ast.Node) Value {
|
||||
e := &Extract{Tuple: tuple, Index: index}
|
||||
e.setType(tuple.Type().(*types.Tuple).At(index).Type())
|
||||
return f.emit(e, source)
|
||||
}
|
||||
|
||||
// emitTypeAssert emits to f a type assertion value := x.(t) and
|
||||
// returns the value. x.Type() must be an interface.
|
||||
func emitTypeAssert(f *Function, x Value, t types.Type, source ast.Node) Value {
|
||||
a := &TypeAssert{X: x, AssertedType: t}
|
||||
a.setType(t)
|
||||
return f.emit(a, source)
|
||||
}
|
||||
|
||||
// emitTypeTest emits to f a type test value,ok := x.(t) and returns
|
||||
// a (value, ok) tuple. x.Type() must be an interface.
|
||||
func emitTypeTest(f *Function, x Value, t types.Type, source ast.Node) Value {
|
||||
a := &TypeAssert{
|
||||
X: x,
|
||||
AssertedType: t,
|
||||
CommaOk: true,
|
||||
}
|
||||
a.setType(types.NewTuple(
|
||||
newVar("value", t),
|
||||
varOk,
|
||||
))
|
||||
return f.emit(a, source)
|
||||
}
|
||||
|
||||
// emitTailCall emits to f a function call in tail position. The
|
||||
// caller is responsible for all fields of 'call' except its type.
|
||||
// Intended for wrapper methods.
|
||||
// Precondition: f does/will not use deferred procedure calls.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
func emitTailCall(f *Function, call *Call, source ast.Node) {
|
||||
tresults := f.Signature.Results()
|
||||
nr := tresults.Len()
|
||||
if nr == 1 {
|
||||
call.typ = tresults.At(0).Type()
|
||||
} else {
|
||||
call.typ = tresults
|
||||
}
|
||||
tuple := f.emit(call, source)
|
||||
var ret Return
|
||||
switch nr {
|
||||
case 0:
|
||||
// no-op
|
||||
case 1:
|
||||
ret.Results = []Value{tuple}
|
||||
default:
|
||||
for i := range nr {
|
||||
v := emitExtract(f, tuple, i, source)
|
||||
// TODO(adonovan): in principle, this is required:
|
||||
// v = emitConv(f, o.Type, f.Signature.Results[i].Type)
|
||||
// but in practice emitTailCall is only used when
|
||||
// the types exactly match.
|
||||
ret.Results = append(ret.Results, v)
|
||||
}
|
||||
}
|
||||
|
||||
f.Exit = f.newBasicBlock("exit")
|
||||
emitJump(f, f.Exit, source)
|
||||
f.currentBlock = f.Exit
|
||||
f.emit(&ret, source)
|
||||
f.currentBlock = nil
|
||||
}
|
||||
|
||||
func emitCall(fn *Function, call *Call, source ast.Node) Value {
|
||||
res := fn.emit(call, source)
|
||||
|
||||
callee := call.Call.StaticCallee()
|
||||
if callee != nil &&
|
||||
callee.object != nil &&
|
||||
fn.Prog.noReturn != nil &&
|
||||
fn.Prog.noReturn(callee.object) {
|
||||
// Call doesn't return normally. Either it doesn't return at all
|
||||
// (infinitely blocked or exitting the process), or it unwinds the stack
|
||||
// (panic, runtime.Goexit). In case it unwinds, jump to the exit block.
|
||||
fn.emit(new(Jump), source)
|
||||
addEdge(fn.currentBlock, fn.Exit)
|
||||
fn.currentBlock = fn.newBasicBlock("unreachable")
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// emitImplicitSelections emits to f code to apply the sequence of
|
||||
// implicit field selections specified by indices to base value v, and
|
||||
// returns the selected value.
|
||||
//
|
||||
// If v is the address of a struct, the result will be the address of
|
||||
// a field; if it is the value of a struct, the result will be the
|
||||
// value of a field.
|
||||
func emitImplicitSelections(f *Function, v Value, indices []int, source ast.Node) Value {
|
||||
for _, index := range indices {
|
||||
// We may have a generic type containing a pointer, or a pointer to a generic type containing a struct. A
|
||||
// pointer to a generic containing a pointer to a struct shouldn't be possible because the outer pointer gets
|
||||
// dereferenced implicitly before we get here.
|
||||
fld := typeutil.CoreType(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, source)
|
||||
// Load the field's value iff indirectly embedded.
|
||||
if isPointer(fld.Type()) {
|
||||
v = emitLoad(f, v, source)
|
||||
}
|
||||
} else {
|
||||
instr := &Field{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setType(fld.Type())
|
||||
v = f.emit(instr, source)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// emitFieldSelection emits to f code to select the index'th field of v.
|
||||
//
|
||||
// If wantAddr, the input must be a pointer-to-struct and the result
|
||||
// will be the field's address; otherwise the result will be the
|
||||
// field's value.
|
||||
// Ident id is used for position and debug info.
|
||||
func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value {
|
||||
// We may have a generic type containing a pointer, or a pointer to a generic type containing a struct. A
|
||||
// pointer to a generic containing a pointer to a struct shouldn't be possible because the outer pointer gets
|
||||
// dereferenced implicitly before we get here.
|
||||
vut := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct)
|
||||
fld := vut.Field(index)
|
||||
if isPointer(v.Type()) {
|
||||
instr := &FieldAddr{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setSource(id)
|
||||
instr.setType(types.NewPointer(fld.Type()))
|
||||
v = f.emit(instr, id)
|
||||
// Load the field's value iff we don't want its address.
|
||||
if !wantAddr {
|
||||
v = emitLoad(f, v, id)
|
||||
}
|
||||
} else {
|
||||
instr := &Field{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setSource(id)
|
||||
instr.setType(fld.Type())
|
||||
v = f.emit(instr, id)
|
||||
}
|
||||
emitDebugRef(f, id, v, wantAddr)
|
||||
return v
|
||||
}
|
||||
|
||||
// zeroValue emits to f code to produce a zero value of type t,
|
||||
// and returns it.
|
||||
func zeroValue(f *Function, t types.Type, source ast.Node) Value {
|
||||
return emitConst(f, zeroConst(t, source))
|
||||
}
|
||||
|
||||
type constKey struct {
|
||||
typ types.Type
|
||||
value constant.Value
|
||||
source ast.Node
|
||||
}
|
||||
|
||||
func emitConst(f *Function, c Constant) Constant {
|
||||
if f.consts == nil {
|
||||
f.consts = map[constKey]constValue{}
|
||||
}
|
||||
|
||||
typ := c.Type()
|
||||
var val constant.Value
|
||||
switch c := c.(type) {
|
||||
case *Const:
|
||||
val = c.Value
|
||||
case *ArrayConst, *GenericConst:
|
||||
// These can only represent zero values, so all we need is the type
|
||||
case *AggregateConst:
|
||||
candidates, _ := f.aggregateConsts.At(c.typ)
|
||||
for _, candidate := range candidates {
|
||||
if c.equal(candidate) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
|
||||
for i := range c.Values {
|
||||
c.Values[i] = emitConst(f, c.Values[i].(Constant))
|
||||
}
|
||||
|
||||
c.setBlock(f.Blocks[0])
|
||||
rands := c.Operands(nil)
|
||||
updateOperandsReferrers(c, rands)
|
||||
candidates = append(candidates, c)
|
||||
f.aggregateConsts.Set(c.typ, candidates)
|
||||
return c
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected type %T", c))
|
||||
}
|
||||
k := constKey{
|
||||
typ: typ,
|
||||
value: val,
|
||||
source: c.Source(),
|
||||
}
|
||||
dup, ok := f.consts[k]
|
||||
if ok {
|
||||
return dup.c
|
||||
} else {
|
||||
c.setBlock(f.Blocks[0])
|
||||
f.consts[k] = constValue{
|
||||
c: c,
|
||||
idx: len(f.consts),
|
||||
}
|
||||
rands := c.Operands(nil)
|
||||
updateOperandsReferrers(c, rands)
|
||||
return c
|
||||
}
|
||||
}
|
||||
1090
vendor/honnef.co/go/tools/go/ir/func.go
vendored
Normal file
1090
vendor/honnef.co/go/tools/go/ir/func.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1126
vendor/honnef.co/go/tools/go/ir/html.go
vendored
Normal file
1126
vendor/honnef.co/go/tools/go/ir/html.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
183
vendor/honnef.co/go/tools/go/ir/irutil/load.go
vendored
Normal file
183
vendor/honnef.co/go/tools/go/ir/irutil/load.go
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package irutil
|
||||
|
||||
// This file defines utility functions for constructing programs in IR form.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/go/ir"
|
||||
|
||||
//lint:ignore SA1019 go/loader is deprecated, but works fine for our tests
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
// Which function, if any, to print in HTML form
|
||||
PrintFunc string
|
||||
}
|
||||
|
||||
// Packages creates an IR program for a set of packages.
|
||||
//
|
||||
// The packages must have been loaded from source syntax using the
|
||||
// golang.org/x/tools/go/packages.Load function in LoadSyntax or
|
||||
// LoadAllSyntax mode.
|
||||
//
|
||||
// Packages creates an IR package for each well-typed package in the
|
||||
// initial list, plus all their dependencies. The resulting list of
|
||||
// packages corresponds to the list of initial packages, and may contain
|
||||
// a nil if IR code could not be constructed for the corresponding initial
|
||||
// package due to type errors.
|
||||
//
|
||||
// Code for bodies of functions is not built until Build is called on
|
||||
// the resulting Program. IR code is constructed only for the initial
|
||||
// packages with well-typed syntax trees.
|
||||
//
|
||||
// The mode parameter controls diagnostics and checking during IR construction.
|
||||
func Packages(initial []*packages.Package, mode ir.BuilderMode, opts *Options) (*ir.Program, []*ir.Package) {
|
||||
return doPackages(initial, mode, false, opts)
|
||||
}
|
||||
|
||||
// AllPackages creates an IR program for a set of packages plus all
|
||||
// their dependencies.
|
||||
//
|
||||
// The packages must have been loaded from source syntax using the
|
||||
// golang.org/x/tools/go/packages.Load function in LoadAllSyntax mode.
|
||||
//
|
||||
// AllPackages creates an IR package for each well-typed package in the
|
||||
// initial list, plus all their dependencies. The resulting list of
|
||||
// packages corresponds to the list of initial packages, and may contain
|
||||
// a nil if IR code could not be constructed for the corresponding
|
||||
// initial package due to type errors.
|
||||
//
|
||||
// Code for bodies of functions is not built until Build is called on
|
||||
// the resulting Program. IR code is constructed for all packages with
|
||||
// well-typed syntax trees.
|
||||
//
|
||||
// The mode parameter controls diagnostics and checking during IR construction.
|
||||
func AllPackages(initial []*packages.Package, mode ir.BuilderMode, opts *Options) (*ir.Program, []*ir.Package) {
|
||||
return doPackages(initial, mode, true, opts)
|
||||
}
|
||||
|
||||
func doPackages(initial []*packages.Package, mode ir.BuilderMode, deps bool, opts *Options) (*ir.Program, []*ir.Package) {
|
||||
|
||||
var fset *token.FileSet
|
||||
if len(initial) > 0 {
|
||||
fset = initial[0].Fset
|
||||
}
|
||||
|
||||
prog := ir.NewProgram(fset, mode)
|
||||
if opts != nil {
|
||||
prog.PrintFunc = opts.PrintFunc
|
||||
}
|
||||
|
||||
isInitial := make(map[*packages.Package]bool, len(initial))
|
||||
for _, p := range initial {
|
||||
isInitial[p] = true
|
||||
}
|
||||
|
||||
irmap := make(map[*packages.Package]*ir.Package)
|
||||
packages.Visit(initial, nil, func(p *packages.Package) {
|
||||
if p.Types != nil && !p.IllTyped {
|
||||
var files []*ast.File
|
||||
if deps || isInitial[p] {
|
||||
files = p.Syntax
|
||||
}
|
||||
irmap[p] = prog.CreatePackage(p.Types, files, p.TypesInfo, true)
|
||||
}
|
||||
})
|
||||
|
||||
var irpkgs []*ir.Package
|
||||
for _, p := range initial {
|
||||
irpkgs = append(irpkgs, irmap[p]) // may be nil
|
||||
}
|
||||
return prog, irpkgs
|
||||
}
|
||||
|
||||
// CreateProgram returns a new program in IR form, given a program
|
||||
// loaded from source. An IR 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.
|
||||
//
|
||||
// The mode parameter controls diagnostics and checking during IR construction.
|
||||
//
|
||||
// Deprecated: use golang.org/x/tools/go/packages and the Packages
|
||||
// function instead; see ir.ExampleLoadPackages.
|
||||
func CreateProgram(lprog *loader.Program, mode ir.BuilderMode) *ir.Program {
|
||||
prog := ir.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 IR 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; IR code cannot be built for
|
||||
// those packages. BuildPackage then constructs an ir.Program with all
|
||||
// dependency packages created, and builds and returns the IR package
|
||||
// corresponding to pkg.
|
||||
//
|
||||
// The caller must have set pkg.Path() to the import path.
|
||||
//
|
||||
// The operation fails if there were any type-checking or import errors.
|
||||
//
|
||||
// See ../ir/example_test.go for an example.
|
||||
func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ir.BuilderMode) (*ir.Package, *types.Info, error) {
|
||||
if fset == nil {
|
||||
panic("no token.FileSet")
|
||||
}
|
||||
if pkg.Path() == "" {
|
||||
panic("package has no import path")
|
||||
}
|
||||
|
||||
info := &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Implicits: make(map[ast.Node]types.Object),
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
Instances: make(map[*ast.Ident]types.Instance),
|
||||
FileVersions: make(map[*ast.File]string),
|
||||
}
|
||||
if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
prog := ir.NewProgram(fset, mode)
|
||||
|
||||
// Create IR packages for all imports.
|
||||
// Order is not significant.
|
||||
created := make(map[*types.Package]bool)
|
||||
var createAll func(pkgs []*types.Package)
|
||||
createAll = func(pkgs []*types.Package) {
|
||||
for _, p := range pkgs {
|
||||
if !created[p] {
|
||||
created[p] = true
|
||||
prog.CreatePackage(p, nil, nil, true)
|
||||
createAll(p.Imports())
|
||||
}
|
||||
}
|
||||
}
|
||||
createAll(pkg.Imports())
|
||||
|
||||
// Create and build the primary package.
|
||||
irpkg := prog.CreatePackage(pkg, files, info, false)
|
||||
irpkg.Build()
|
||||
return irpkg, info, nil
|
||||
}
|
||||
54
vendor/honnef.co/go/tools/go/ir/irutil/loops.go
vendored
Normal file
54
vendor/honnef.co/go/tools/go/ir/irutil/loops.go
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package irutil
|
||||
|
||||
import "honnef.co/go/tools/go/ir"
|
||||
|
||||
type Loop struct{ *ir.BlockSet }
|
||||
|
||||
func FindLoops(fn *ir.Function) []Loop {
|
||||
if fn.Blocks == nil {
|
||||
return nil
|
||||
}
|
||||
tree := fn.DomPreorder()
|
||||
var sets []Loop
|
||||
for _, h := range tree {
|
||||
for _, n := range h.Preds {
|
||||
if !h.Dominates(n) {
|
||||
continue
|
||||
}
|
||||
// n is a back-edge to h
|
||||
// h is the loop header
|
||||
if n == h {
|
||||
set := Loop{ir.NewBlockSet(len(fn.Blocks))}
|
||||
set.Add(n)
|
||||
sets = append(sets, set)
|
||||
continue
|
||||
}
|
||||
set := Loop{ir.NewBlockSet(len(fn.Blocks))}
|
||||
set.Add(h)
|
||||
set.Add(n)
|
||||
for _, b := range allPredsBut(n, h, nil) {
|
||||
set.Add(b)
|
||||
}
|
||||
sets = append(sets, set)
|
||||
}
|
||||
}
|
||||
return sets
|
||||
}
|
||||
|
||||
func allPredsBut(b, but *ir.BasicBlock, list []*ir.BasicBlock) []*ir.BasicBlock {
|
||||
outer:
|
||||
for _, pred := range b.Preds {
|
||||
if pred == but {
|
||||
continue
|
||||
}
|
||||
for _, p := range list {
|
||||
// TODO improve big-o complexity of this function
|
||||
if pred == p {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
list = append(list, pred)
|
||||
list = allPredsBut(pred, but, list)
|
||||
}
|
||||
return list
|
||||
}
|
||||
32
vendor/honnef.co/go/tools/go/ir/irutil/stub.go
vendored
Normal file
32
vendor/honnef.co/go/tools/go/ir/irutil/stub.go
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package irutil
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/go/ir"
|
||||
)
|
||||
|
||||
// IsStub reports whether a function is a stub. A function is
|
||||
// considered a stub if it has no instructions or if all it does is
|
||||
// return a constant value.
|
||||
func IsStub(fn *ir.Function) bool {
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr.(type) {
|
||||
case *ir.Const:
|
||||
// const naturally has no side-effects
|
||||
case *ir.Panic:
|
||||
// panic is a stub if it only uses constants
|
||||
case *ir.Return:
|
||||
// return is a stub if it only uses constants
|
||||
case *ir.DebugRef:
|
||||
case *ir.Jump:
|
||||
// if there are no disallowed instructions, then we're
|
||||
// only jumping to the exit block (or possibly
|
||||
// somewhere else that's stubby?)
|
||||
default:
|
||||
// all other instructions are assumed to do actual work
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
260
vendor/honnef.co/go/tools/go/ir/irutil/switch.go
vendored
Normal file
260
vendor/honnef.co/go/tools/go/ir/irutil/switch.go
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package irutil
|
||||
|
||||
// This file implements discovery of switch and type-switch constructs
|
||||
// from low-level control flow.
|
||||
//
|
||||
// Many techniques exist for compiling a high-level switch with
|
||||
// constant cases to efficient machine code. The optimal choice will
|
||||
// depend on the data type, the specific case values, the code in the
|
||||
// body of each case, and the hardware.
|
||||
// Some examples:
|
||||
// - a lookup table (for a switch that maps constants to constants)
|
||||
// - a computed goto
|
||||
// - a binary tree
|
||||
// - a perfect hash
|
||||
// - a two-level switch (to partition constant strings by their first byte).
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/go/ir"
|
||||
)
|
||||
|
||||
// A ConstCase represents a single constant comparison.
|
||||
// It is part of a Switch.
|
||||
type ConstCase struct {
|
||||
Block *ir.BasicBlock // block performing the comparison
|
||||
Body *ir.BasicBlock // body of the case
|
||||
Value *ir.Const // case comparand
|
||||
}
|
||||
|
||||
// A TypeCase represents a single type assertion.
|
||||
// It is part of a Switch.
|
||||
type TypeCase struct {
|
||||
Block *ir.BasicBlock // block performing the type assert
|
||||
Body *ir.BasicBlock // body of the case
|
||||
Type types.Type // case type
|
||||
Binding ir.Value // value bound by this case
|
||||
}
|
||||
|
||||
// A Switch is a logical high-level control flow operation
|
||||
// (a multiway branch) discovered by analysis of a CFG containing
|
||||
// only if/else chains. It is not part of the ir.Instruction set.
|
||||
//
|
||||
// One of ConstCases and TypeCases has length >= 2;
|
||||
// the other is nil.
|
||||
//
|
||||
// In a value switch, the list of cases may contain duplicate constants.
|
||||
// A type switch may contain duplicate types, or types assignable
|
||||
// to an interface type also in the list.
|
||||
// TODO(adonovan): eliminate such duplicates.
|
||||
type Switch struct {
|
||||
Start *ir.BasicBlock // block containing start of if/else chain
|
||||
X ir.Value // the switch operand
|
||||
ConstCases []ConstCase // ordered list of constant comparisons
|
||||
TypeCases []TypeCase // ordered list of type assertions
|
||||
Default *ir.BasicBlock // successor if all comparisons fail
|
||||
}
|
||||
|
||||
func (sw *Switch) String() string {
|
||||
// We represent each block by the String() of its
|
||||
// first Instruction, e.g. "print(42:int)".
|
||||
var buf bytes.Buffer
|
||||
if sw.ConstCases != nil {
|
||||
fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name())
|
||||
for _, c := range sw.ConstCases {
|
||||
fmt.Fprintf(&buf, "case %s: %s\n", c.Value.Name(), c.Body.Instrs[0])
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name())
|
||||
for _, c := range sw.TypeCases {
|
||||
fmt.Fprintf(&buf, "case %s %s: %s\n",
|
||||
c.Binding.Name(), c.Type, c.Body.Instrs[0])
|
||||
}
|
||||
}
|
||||
if sw.Default != nil {
|
||||
fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0])
|
||||
}
|
||||
fmt.Fprintf(&buf, "}")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Switches examines the control-flow graph of fn and returns the
|
||||
// set of inferred value and type switches. A value switch tests an
|
||||
// ir.Value for equality against two or more compile-time constant
|
||||
// values. Switches involving link-time constants (addresses) are
|
||||
// ignored. A type switch type-asserts an ir.Value against two or
|
||||
// more types.
|
||||
//
|
||||
// The switches are returned in dominance order.
|
||||
//
|
||||
// The resulting switches do not necessarily correspond to uses of the
|
||||
// 'switch' keyword in the source: for example, a single source-level
|
||||
// switch statement with non-constant cases may result in zero, one or
|
||||
// many Switches, one per plural sequence of constant cases.
|
||||
// Switches may even be inferred from if/else- or goto-based control flow.
|
||||
// (In general, the control flow constructs of the source program
|
||||
// cannot be faithfully reproduced from the IR.)
|
||||
func Switches(fn *ir.Function) []Switch {
|
||||
// Traverse the CFG in dominance order, so we don't
|
||||
// enter an if/else-chain in the middle.
|
||||
var switches []Switch
|
||||
seen := make(map[*ir.BasicBlock]bool) // TODO(adonovan): opt: use ir.blockSet
|
||||
for _, b := range fn.DomPreorder() {
|
||||
if x, k := isComparisonBlock(b); x != nil {
|
||||
// Block b starts a switch.
|
||||
sw := Switch{Start: b, X: x}
|
||||
valueSwitch(&sw, k, seen)
|
||||
if len(sw.ConstCases) > 1 {
|
||||
switches = append(switches, sw)
|
||||
}
|
||||
}
|
||||
|
||||
if y, x, T := isTypeAssertBlock(b); y != nil {
|
||||
// Block b starts a type switch.
|
||||
sw := Switch{Start: b, X: x}
|
||||
typeSwitch(&sw, y, T, seen)
|
||||
if len(sw.TypeCases) > 1 {
|
||||
switches = append(switches, sw)
|
||||
}
|
||||
}
|
||||
}
|
||||
return switches
|
||||
}
|
||||
|
||||
func isSameX(x1 ir.Value, x2 ir.Value) bool {
|
||||
if x1 == x2 {
|
||||
return true
|
||||
}
|
||||
if x2, ok := x2.(*ir.Sigma); ok {
|
||||
return isSameX(x1, x2.X)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) {
|
||||
b := sw.Start
|
||||
x := sw.X
|
||||
for isSameX(sw.X, x) {
|
||||
if seen[b] {
|
||||
break
|
||||
}
|
||||
seen[b] = true
|
||||
|
||||
sw.ConstCases = append(sw.ConstCases, ConstCase{
|
||||
Block: b,
|
||||
Body: b.Succs[0],
|
||||
Value: k,
|
||||
})
|
||||
b = b.Succs[1]
|
||||
n := 0
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr.(type) {
|
||||
case *ir.If, *ir.BinOp:
|
||||
n++
|
||||
case *ir.Sigma, *ir.Phi, *ir.DebugRef:
|
||||
default:
|
||||
n += 1000
|
||||
}
|
||||
}
|
||||
if n != 2 {
|
||||
// Block b contains not just 'if x == k' and σ/ϕ nodes,
|
||||
// so it may have side effects that
|
||||
// make it unsafe to elide.
|
||||
break
|
||||
}
|
||||
if len(b.Preds) != 1 {
|
||||
// Block b has multiple predecessors,
|
||||
// so it cannot be treated as a case.
|
||||
break
|
||||
}
|
||||
x, k = isComparisonBlock(b)
|
||||
}
|
||||
sw.Default = b
|
||||
}
|
||||
|
||||
func typeSwitch(sw *Switch, y ir.Value, T types.Type, seen map[*ir.BasicBlock]bool) {
|
||||
b := sw.Start
|
||||
x := sw.X
|
||||
for isSameX(sw.X, x) {
|
||||
if seen[b] {
|
||||
break
|
||||
}
|
||||
seen[b] = true
|
||||
|
||||
sw.TypeCases = append(sw.TypeCases, TypeCase{
|
||||
Block: b,
|
||||
Body: b.Succs[0],
|
||||
Type: T,
|
||||
Binding: y,
|
||||
})
|
||||
b = b.Succs[1]
|
||||
n := 0
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr.(type) {
|
||||
case *ir.TypeAssert, *ir.Extract, *ir.If:
|
||||
n++
|
||||
case *ir.Sigma, *ir.Phi:
|
||||
default:
|
||||
n += 1000
|
||||
}
|
||||
}
|
||||
if n != 4 {
|
||||
// Block b contains not just
|
||||
// {TypeAssert; Extract #0; Extract #1; If}
|
||||
// so it may have side effects that
|
||||
// make it unsafe to elide.
|
||||
break
|
||||
}
|
||||
if len(b.Preds) != 1 {
|
||||
// Block b has multiple predecessors,
|
||||
// so it cannot be treated as a case.
|
||||
break
|
||||
}
|
||||
y, x, T = isTypeAssertBlock(b)
|
||||
}
|
||||
sw.Default = b
|
||||
}
|
||||
|
||||
// isComparisonBlock returns the operands (v, k) if a block ends with
|
||||
// a comparison v==k, where k is a compile-time constant.
|
||||
func isComparisonBlock(b *ir.BasicBlock) (v ir.Value, k *ir.Const) {
|
||||
if n := len(b.Instrs); n >= 2 {
|
||||
if i, ok := b.Instrs[n-1].(*ir.If); ok {
|
||||
if binop, ok := i.Cond.(*ir.BinOp); ok && binop.Block() == b && binop.Op == token.EQL {
|
||||
if k, ok := binop.Y.(*ir.Const); ok {
|
||||
return binop.X, k
|
||||
}
|
||||
if k, ok := binop.X.(*ir.Const); ok {
|
||||
return binop.Y, k
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// isTypeAssertBlock returns the operands (y, x, T) if a block ends with
|
||||
// a type assertion "if y, ok := x.(T); ok {".
|
||||
func isTypeAssertBlock(b *ir.BasicBlock) (y, x ir.Value, T types.Type) {
|
||||
if n := len(b.Instrs); n >= 4 {
|
||||
if i, ok := b.Instrs[n-1].(*ir.If); ok {
|
||||
if ext1, ok := i.Cond.(*ir.Extract); ok && ext1.Block() == b && ext1.Index == 1 {
|
||||
if ta, ok := ext1.Tuple.(*ir.TypeAssert); ok && ta.Block() == b {
|
||||
// hack: relies upon instruction ordering.
|
||||
if ext0, ok := b.Instrs[n-3].(*ir.Extract); ok {
|
||||
return ext0, ta.X, ta.AssertedType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
70
vendor/honnef.co/go/tools/go/ir/irutil/terminates.go
vendored
Normal file
70
vendor/honnef.co/go/tools/go/ir/irutil/terminates.go
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package irutil
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/go/ir"
|
||||
)
|
||||
|
||||
// Terminates reports whether fn is supposed to return, that is if it
|
||||
// has at least one theoretic path that returns from the function.
|
||||
// Explicit panics do not count as terminating.
|
||||
func Terminates(fn *ir.Function) bool {
|
||||
if fn.Blocks == nil {
|
||||
// assuming that a function terminates is the conservative
|
||||
// choice
|
||||
return true
|
||||
}
|
||||
|
||||
for _, block := range fn.Blocks {
|
||||
if _, ok := block.Control().(*ir.Return); ok {
|
||||
if len(block.Preds) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, pred := range block.Preds {
|
||||
switch ctrl := pred.Control().(type) {
|
||||
case *ir.Panic:
|
||||
// explicit panics do not count as terminating
|
||||
case *ir.If:
|
||||
// Check if we got here by receiving from a closed
|
||||
// time.Tick channel – this cannot happen at
|
||||
// runtime and thus doesn't constitute termination
|
||||
iff := ctrl
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
ex, ok := iff.Cond.(*ir.Extract)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if ex.Index != 1 {
|
||||
return true
|
||||
}
|
||||
recv, ok := ex.Tuple.(*ir.Recv)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
call, ok := recv.Chan.(*ir.Call)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
fn, ok := call.Common().Value.(*ir.Function)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
fn2, ok := fn.Object().(*types.Func)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if fn2.FullName() != "time.Tick" {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
// we've reached the exit block
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
174
vendor/honnef.co/go/tools/go/ir/irutil/util.go
vendored
Normal file
174
vendor/honnef.co/go/tools/go/ir/irutil/util.go
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
package irutil
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
func Reachable(from, to *ir.BasicBlock) bool {
|
||||
if from == to {
|
||||
return true
|
||||
}
|
||||
if from.Dominates(to) {
|
||||
return true
|
||||
}
|
||||
|
||||
found := false
|
||||
Walk(from, func(b *ir.BasicBlock) bool {
|
||||
if b == to {
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
func Walk(b *ir.BasicBlock, fn func(*ir.BasicBlock) bool) {
|
||||
seen := map[*ir.BasicBlock]bool{}
|
||||
wl := []*ir.BasicBlock{b}
|
||||
for len(wl) > 0 {
|
||||
b := wl[len(wl)-1]
|
||||
wl = wl[:len(wl)-1]
|
||||
if seen[b] {
|
||||
continue
|
||||
}
|
||||
seen[b] = true
|
||||
if !fn(b) {
|
||||
continue
|
||||
}
|
||||
wl = append(wl, b.Succs...)
|
||||
}
|
||||
}
|
||||
|
||||
func Vararg(x *ir.Slice) ([]ir.Value, bool) {
|
||||
var out []ir.Value
|
||||
alloc, ok := ir.Unwrap(x.X).(*ir.Alloc)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
var checkAlloc func(alloc ir.Value) bool
|
||||
checkAlloc = func(alloc ir.Value) bool {
|
||||
for _, ref := range *alloc.Referrers() {
|
||||
if ref == x {
|
||||
continue
|
||||
}
|
||||
if ref.Block() != x.Block() {
|
||||
return false
|
||||
}
|
||||
switch ref := ref.(type) {
|
||||
case *ir.IndexAddr:
|
||||
idx := ref
|
||||
if len(*idx.Referrers()) != 1 {
|
||||
return false
|
||||
}
|
||||
store, ok := (*idx.Referrers())[0].(*ir.Store)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
out = append(out, store.Val)
|
||||
case *ir.Copy:
|
||||
if !checkAlloc(ref) {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
if !checkAlloc(alloc) {
|
||||
return nil, false
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
func CallName(call *ir.CallCommon) string {
|
||||
if call.IsInvoke() {
|
||||
return ""
|
||||
}
|
||||
switch v := call.Value.(type) {
|
||||
case *ir.Function:
|
||||
fn, ok := v.Object().(*types.Func)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return typeutil.FuncName(fn)
|
||||
case *ir.Builtin:
|
||||
return v.Name()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func IsCallTo(call *ir.CallCommon, name string) bool { return CallName(call) == name }
|
||||
|
||||
func IsCallToAny(call *ir.CallCommon, names ...string) bool {
|
||||
q := CallName(call)
|
||||
return slices.Contains(names, q)
|
||||
}
|
||||
|
||||
func FilterDebug(instr []ir.Instruction) []ir.Instruction {
|
||||
var out []ir.Instruction
|
||||
for _, ins := range instr {
|
||||
if _, ok := ins.(*ir.DebugRef); !ok {
|
||||
out = append(out, ins)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func IsExample(fn *ir.Function) bool {
|
||||
if !strings.HasPrefix(fn.Name(), "Example") {
|
||||
return false
|
||||
}
|
||||
f := fn.Prog.Fset.File(fn.Pos())
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
return strings.HasSuffix(f.Name(), "_test.go")
|
||||
}
|
||||
|
||||
// Flatten recursively returns the underlying value of an ir.Sigma or
|
||||
// ir.Phi node. If all edges in an ir.Phi node are the same (after
|
||||
// flattening), the flattened edge will get returned. If flattening is
|
||||
// not possible, nil is returned.
|
||||
func Flatten(v ir.Value) ir.Value {
|
||||
failed := false
|
||||
seen := map[ir.Value]struct{}{}
|
||||
var out ir.Value
|
||||
var dfs func(v ir.Value)
|
||||
dfs = func(v ir.Value) {
|
||||
if failed {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[v]; ok {
|
||||
return
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
|
||||
switch v := v.(type) {
|
||||
case *ir.Sigma:
|
||||
dfs(v.X)
|
||||
case *ir.Phi:
|
||||
for _, e := range v.Edges {
|
||||
dfs(e)
|
||||
}
|
||||
default:
|
||||
if out == nil {
|
||||
out = v
|
||||
} else if out != v {
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
dfs(v)
|
||||
|
||||
if failed {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
78
vendor/honnef.co/go/tools/go/ir/irutil/visit.go
vendored
Normal file
78
vendor/honnef.co/go/tools/go/ir/irutil/visit.go
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package irutil
|
||||
|
||||
import "honnef.co/go/tools/go/ir"
|
||||
|
||||
// This file defines utilities for visiting the IR of
|
||||
// a Program.
|
||||
//
|
||||
// TODO(adonovan): test coverage.
|
||||
|
||||
// AllFunctions finds and returns the set of functions potentially
|
||||
// needed by program prog, as determined by a simple linker-style
|
||||
// reachability algorithm starting from the members and method-sets of
|
||||
// each package. The result may include anonymous functions and
|
||||
// synthetic wrappers.
|
||||
//
|
||||
// Precondition: all packages are built.
|
||||
func AllFunctions(prog *ir.Program) map[*ir.Function]bool {
|
||||
visit := visitor{
|
||||
prog: prog,
|
||||
seen: make(map[*ir.Function]bool),
|
||||
}
|
||||
visit.program()
|
||||
return visit.seen
|
||||
}
|
||||
|
||||
type visitor struct {
|
||||
prog *ir.Program
|
||||
seen map[*ir.Function]bool
|
||||
}
|
||||
|
||||
func (visit *visitor) program() {
|
||||
for _, pkg := range visit.prog.AllPackages() {
|
||||
for _, mem := range pkg.Members {
|
||||
if fn, ok := mem.(*ir.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 *ir.Function) {
|
||||
if !visit.seen[fn] {
|
||||
visit.seen[fn] = true
|
||||
var buf [10]*ir.Value // avoid alloc in common case
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
for _, op := range instr.Operands(buf[:0]) {
|
||||
if fn, ok := (*op).(*ir.Function); ok {
|
||||
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 []*ir.Package) []*ir.Package {
|
||||
var mains []*ir.Package
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil {
|
||||
mains = append(mains, pkg)
|
||||
}
|
||||
}
|
||||
return mains
|
||||
}
|
||||
1817
vendor/honnef.co/go/tools/go/ir/lift.go
vendored
Normal file
1817
vendor/honnef.co/go/tools/go/ir/lift.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
175
vendor/honnef.co/go/tools/go/ir/lvalue.go
vendored
Normal file
175
vendor/honnef.co/go/tools/go/ir/lvalue.go
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// lvalues are the union of addressable expressions and map-index
|
||||
// expressions.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// An lvalue represents an assignable location that may appear on the
|
||||
// left-hand side of an assignment. This is a generalization of a
|
||||
// pointer to permit updates to elements of maps.
|
||||
type lvalue interface {
|
||||
store(fn *Function, v Value, source ast.Node) // stores v into the location
|
||||
load(fn *Function, source ast.Node) Value // loads the contents of the location
|
||||
address(fn *Function) Value // address of the location
|
||||
typ() types.Type // returns the type of the location
|
||||
}
|
||||
|
||||
// An address is an lvalue represented by a true pointer.
|
||||
type address struct {
|
||||
addr Value
|
||||
expr ast.Expr // source syntax of the value (not address) [debug mode]
|
||||
}
|
||||
|
||||
func (a *address) load(fn *Function, source ast.Node) Value {
|
||||
return emitLoad(fn, a.addr, source)
|
||||
}
|
||||
|
||||
func (a *address) store(fn *Function, v Value, source ast.Node) {
|
||||
store := emitStore(fn, a.addr, v, source)
|
||||
if a.expr != nil {
|
||||
// store.Val is v, converted for assignability.
|
||||
emitDebugRef(fn, a.expr, store.Val, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *address) address(fn *Function) Value {
|
||||
if a.expr != nil {
|
||||
emitDebugRef(fn, a.expr, a.addr, true)
|
||||
}
|
||||
return a.addr
|
||||
}
|
||||
|
||||
func (a *address) typ() types.Type {
|
||||
return deref(a.addr.Type())
|
||||
}
|
||||
|
||||
type compositeElement struct {
|
||||
cv *CompositeValue
|
||||
idx int
|
||||
t types.Type
|
||||
expr ast.Expr
|
||||
}
|
||||
|
||||
func (ce *compositeElement) load(fn *Function, source ast.Node) Value {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (ce *compositeElement) store(fn *Function, v Value, source ast.Node) {
|
||||
v = emitConv(fn, v, ce.t, source)
|
||||
ce.cv.Values[ce.idx] = v
|
||||
if ce.expr != nil {
|
||||
// store.Val is v, converted for assignability.
|
||||
emitDebugRef(fn, ce.expr, v, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (ce *compositeElement) address(fn *Function) Value {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (ce *compositeElement) typ() types.Type {
|
||||
return ce.t
|
||||
}
|
||||
|
||||
// An element is an lvalue represented by m[k], the location of an
|
||||
// element of a map. These locations are not addressable
|
||||
// since pointers cannot be formed from them, but they do support
|
||||
// load() and store().
|
||||
type element struct {
|
||||
m, k Value // map
|
||||
t types.Type // map element type
|
||||
}
|
||||
|
||||
func (e *element) load(fn *Function, source ast.Node) Value {
|
||||
l := &MapLookup{
|
||||
X: e.m,
|
||||
Index: e.k,
|
||||
}
|
||||
l.setType(e.t)
|
||||
return fn.emit(l, source)
|
||||
}
|
||||
|
||||
func (e *element) store(fn *Function, v Value, source ast.Node) {
|
||||
up := &MapUpdate{
|
||||
Map: e.m,
|
||||
Key: e.k,
|
||||
Value: emitConv(fn, v, e.t, source),
|
||||
}
|
||||
fn.emit(up, source)
|
||||
}
|
||||
|
||||
func (e *element) address(fn *Function) Value {
|
||||
panic("map elements are not addressable")
|
||||
}
|
||||
|
||||
func (e *element) typ() types.Type {
|
||||
return e.t
|
||||
}
|
||||
|
||||
// A lazyAddress is an lvalue whose address is the result of an instruction.
|
||||
// These work like an *address except a new address.address() Value
|
||||
// is created on each load, store and address call.
|
||||
// A lazyAddress can be used to control when a side effect (nil pointer
|
||||
// dereference, index out of bounds) of using a location happens.
|
||||
type lazyAddress struct {
|
||||
addr func(fn *Function) Value // emit to fn the computation of the address
|
||||
t types.Type // type of the location
|
||||
expr ast.Expr // source syntax of the value (not address) [debug mode]
|
||||
}
|
||||
|
||||
func (l *lazyAddress) load(fn *Function, source ast.Node) Value {
|
||||
load := emitLoad(fn, l.addr(fn), source)
|
||||
return load
|
||||
}
|
||||
|
||||
func (l *lazyAddress) store(fn *Function, v Value, source ast.Node) {
|
||||
store := emitStore(fn, l.addr(fn), v, source)
|
||||
if l.expr != nil {
|
||||
// store.Val is v, converted for assignability.
|
||||
emitDebugRef(fn, l.expr, store.Val, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lazyAddress) address(fn *Function) Value {
|
||||
addr := l.addr(fn)
|
||||
if l.expr != nil {
|
||||
emitDebugRef(fn, l.expr, addr, true)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
func (l *lazyAddress) typ() types.Type { return l.t }
|
||||
|
||||
// A blank is a dummy variable whose name is "_".
|
||||
// It is not reified: loads are illegal and stores are ignored.
|
||||
type blank struct{}
|
||||
|
||||
func (bl blank) load(fn *Function, source ast.Node) Value {
|
||||
panic("blank.load is illegal")
|
||||
}
|
||||
|
||||
func (bl blank) store(fn *Function, v Value, source ast.Node) {
|
||||
s := &BlankStore{
|
||||
Val: v,
|
||||
}
|
||||
fn.emit(s, source)
|
||||
}
|
||||
|
||||
func (bl blank) address(fn *Function) Value {
|
||||
panic("blank var is not addressable")
|
||||
}
|
||||
|
||||
func (bl blank) typ() types.Type {
|
||||
// This should be the type of the blank Ident; the typechecker
|
||||
// doesn't provide this yet, but fortunately, we don't need it
|
||||
// yet either.
|
||||
panic("blank.typ is unimplemented")
|
||||
}
|
||||
244
vendor/honnef.co/go/tools/go/ir/methods.go
vendored
Normal file
244
vendor/honnef.co/go/tools/go/ir/methods.go
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// This file defines utilities for population of method sets.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
)
|
||||
|
||||
// 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("MethodValue(%s) kind != MethodVal", sel))
|
||||
}
|
||||
T := sel.Recv()
|
||||
if types.IsInterface(T) {
|
||||
return nil // abstract method
|
||||
}
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("MethodValue %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)
|
||||
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 *methodSet) {
|
||||
if v.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 origin := obj.Origin(); origin != obj {
|
||||
// Calling method on instantiated type, create a wrapper that calls the generic type's method
|
||||
base := prog.packageLevelValue(origin)
|
||||
return makeInstance(prog, base.(*Function), obj.Type().(*types.Signature), nil)
|
||||
} else {
|
||||
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); 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 && !types.IsInterface(T) && tmset.Len() > 0 {
|
||||
// Create methods of T.
|
||||
mset := prog.createMethodSet(T)
|
||||
if !mset.complete {
|
||||
mset.complete = true
|
||||
n := tmset.Len()
|
||||
for i := range n {
|
||||
prog.addMethod(mset, tmset.At(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursion over signatures of each method.
|
||||
for method := range tmset.Methods() {
|
||||
sig := method.Type().(*types.Signature)
|
||||
prog.needMethods(sig.Params(), false)
|
||||
prog.needMethods(sig.Results(), false)
|
||||
}
|
||||
|
||||
switch t := T.(type) {
|
||||
case *types.Basic:
|
||||
// nop
|
||||
|
||||
case *types.Interface, *types.TypeParam:
|
||||
// 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)
|
||||
}
|
||||
|
||||
case *types.Alias:
|
||||
prog.needMethods(types.Unalias(t), false)
|
||||
|
||||
default:
|
||||
lint.ExhaustiveTypeSwitch(T)
|
||||
}
|
||||
}
|
||||
104
vendor/honnef.co/go/tools/go/ir/mode.go
vendored
Normal file
104
vendor/honnef.co/go/tools/go/ir/mode.go
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// This file defines the BuilderMode type and its command-line flag.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// BuilderMode is a bitmask of options for diagnostics and checking.
|
||||
//
|
||||
// *BuilderMode satisfies the flag.Value interface. Example:
|
||||
//
|
||||
// var mode = ir.BuilderMode(0)
|
||||
// func init() { flag.Var(&mode, "build", ir.BuilderModeDoc) }
|
||||
type BuilderMode uint
|
||||
|
||||
const (
|
||||
PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout
|
||||
PrintFunctions // Print function IR code to stdout
|
||||
PrintSource // Print source code when printing function IR
|
||||
LogSource // Log source locations as IR builder progresses
|
||||
SanityCheckFunctions // Perform sanity checking of function bodies
|
||||
NaiveForm // Build naïve IR form: don't replace local loads/stores with registers
|
||||
GlobalDebug // Enable debug info for all packages
|
||||
SplitAfterNewInformation // Split live range after we learn something new about a value
|
||||
)
|
||||
|
||||
const BuilderModeDoc = `Options controlling the IR builder.
|
||||
The value is a sequence of zero or more of these symbols:
|
||||
C perform sanity [C]hecking of the IR form.
|
||||
D include [D]ebug info for every function.
|
||||
P print [P]ackage inventory.
|
||||
F print [F]unction IR code.
|
||||
A print [A]ST nodes responsible for IR instructions
|
||||
S log [S]ource locations as IR builder progresses.
|
||||
N build [N]aive IR form: don't replace local loads/stores with registers.
|
||||
I Split live range after a value is used as slice or array index
|
||||
`
|
||||
|
||||
func (m BuilderMode) String() string {
|
||||
var buf bytes.Buffer
|
||||
if m&GlobalDebug != 0 {
|
||||
buf.WriteByte('D')
|
||||
}
|
||||
if m&PrintPackages != 0 {
|
||||
buf.WriteByte('P')
|
||||
}
|
||||
if m&PrintFunctions != 0 {
|
||||
buf.WriteByte('F')
|
||||
}
|
||||
if m&PrintSource != 0 {
|
||||
buf.WriteByte('A')
|
||||
}
|
||||
if m&LogSource != 0 {
|
||||
buf.WriteByte('S')
|
||||
}
|
||||
if m&SanityCheckFunctions != 0 {
|
||||
buf.WriteByte('C')
|
||||
}
|
||||
if m&NaiveForm != 0 {
|
||||
buf.WriteByte('N')
|
||||
}
|
||||
if m&SplitAfterNewInformation != 0 {
|
||||
buf.WriteByte('I')
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Set parses the flag characters in s and updates *m.
|
||||
func (m *BuilderMode) Set(s string) error {
|
||||
var mode BuilderMode
|
||||
for _, c := range s {
|
||||
switch c {
|
||||
case 'D':
|
||||
mode |= GlobalDebug
|
||||
case 'P':
|
||||
mode |= PrintPackages
|
||||
case 'F':
|
||||
mode |= PrintFunctions
|
||||
case 'A':
|
||||
mode |= PrintSource
|
||||
case 'S':
|
||||
mode |= LogSource
|
||||
case 'C':
|
||||
mode |= SanityCheckFunctions
|
||||
case 'N':
|
||||
mode |= NaiveForm
|
||||
case 'I':
|
||||
mode |= SplitAfterNewInformation
|
||||
default:
|
||||
return fmt.Errorf("unknown BuilderMode option: %q", c)
|
||||
}
|
||||
}
|
||||
*m = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns m.
|
||||
func (m BuilderMode) Get() any { return m }
|
||||
539
vendor/honnef.co/go/tools/go/ir/print.go
vendored
Normal file
539
vendor/honnef.co/go/tools/go/ir/print.go
vendored
Normal file
@@ -0,0 +1,539 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// This file implements the String() methods for all Value and
|
||||
// Instruction types.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// relName returns the name of v relative to i.
|
||||
// In most cases, this is identical to v.Name(), but references to
|
||||
// Functions (including methods) and Globals use RelString and
|
||||
// all types are displayed with relType, so that only cross-package
|
||||
// references are package-qualified.
|
||||
func relName(v Value, i Instruction) string {
|
||||
if v == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
var from *types.Package
|
||||
if i != nil {
|
||||
from = i.Parent().pkg()
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case Member: // *Function or *Global
|
||||
return v.RelString(from)
|
||||
}
|
||||
return v.Name()
|
||||
}
|
||||
|
||||
func relType(t types.Type, from *types.Package) string {
|
||||
return types.TypeString(t, types.RelativeTo(from))
|
||||
}
|
||||
|
||||
func relTerm(term *types.Term, from *types.Package) string {
|
||||
s := relType(term.Type(), from)
|
||||
if term.Tilde() {
|
||||
return "~" + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func relString(m Member, from *types.Package) string {
|
||||
// NB: not all globals have an Object (e.g. init$guard),
|
||||
// so use Package().Object not Object.Package().
|
||||
if pkg := m.Package().Pkg; pkg != nil && pkg != from {
|
||||
return fmt.Sprintf("%s.%s", pkg.Path(), m.Name())
|
||||
}
|
||||
return m.Name()
|
||||
}
|
||||
|
||||
// Value.String()
|
||||
//
|
||||
// This method is provided only for debugging.
|
||||
// It never appears in disassembly, which uses Value.Name().
|
||||
|
||||
func (v *Parameter) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("Parameter <%s> {%s}", relType(v.Type(), from), v.name)
|
||||
}
|
||||
|
||||
func (v *FreeVar) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("FreeVar <%s> %s", relType(v.Type(), from), v.Name())
|
||||
}
|
||||
|
||||
func (v *Builtin) String() string {
|
||||
return fmt.Sprintf("Builtin %s", v.Name())
|
||||
}
|
||||
|
||||
// Instruction.String()
|
||||
|
||||
func (v *Alloc) String() string {
|
||||
from := v.Parent().pkg()
|
||||
storage := "Stack"
|
||||
if v.Heap {
|
||||
storage = "Heap"
|
||||
}
|
||||
return fmt.Sprintf("%sAlloc <%s>", storage, relType(v.Type(), from))
|
||||
}
|
||||
|
||||
func (v *Sigma) String() string {
|
||||
from := v.Parent().pkg()
|
||||
s := fmt.Sprintf("Sigma <%s> [b%d] %s", relType(v.Type(), from), v.From.Index, v.X.Name())
|
||||
return s
|
||||
}
|
||||
|
||||
func (v *Phi) String() string {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, "Phi <%s>", v.Type())
|
||||
for i, edge := range v.Edges {
|
||||
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)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func printCall(v *CallCommon, prefix string, instr Instruction) string {
|
||||
var b bytes.Buffer
|
||||
if !v.IsInvoke() {
|
||||
if value, ok := instr.(Value); ok {
|
||||
fmt.Fprintf(&b, "%s <%s> %s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr))
|
||||
} else {
|
||||
fmt.Fprintf(&b, "%s %s", prefix, relName(v.Value, instr))
|
||||
}
|
||||
} else {
|
||||
if value, ok := instr.(Value); ok {
|
||||
fmt.Fprintf(&b, "%sInvoke <%s> %s.%s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr), v.Method.Name())
|
||||
} else {
|
||||
fmt.Fprintf(&b, "%sInvoke %s.%s", prefix, relName(v.Value, instr), v.Method.Name())
|
||||
}
|
||||
}
|
||||
for _, arg := range v.TypeArgs {
|
||||
b.WriteString(" ")
|
||||
b.WriteString(relType(arg, instr.Parent().pkg()))
|
||||
}
|
||||
for _, arg := range v.Args {
|
||||
b.WriteString(" ")
|
||||
b.WriteString(relName(arg, instr))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c *CallCommon) String() string {
|
||||
return printCall(c, "", nil)
|
||||
}
|
||||
|
||||
func (v *Call) String() string {
|
||||
return printCall(&v.Call, "Call", v)
|
||||
}
|
||||
|
||||
func (v *BinOp) String() string {
|
||||
return fmt.Sprintf("BinOp <%s> {%s} %s %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v), relName(v.Y, v))
|
||||
}
|
||||
|
||||
func (v *UnOp) String() string {
|
||||
return fmt.Sprintf("UnOp <%s> {%s} %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v))
|
||||
}
|
||||
|
||||
func (v *Load) String() string {
|
||||
return fmt.Sprintf("Load <%s> %s", relType(v.Type(), v.Parent().pkg()), relName(v.X, v))
|
||||
}
|
||||
|
||||
func (v *Copy) String() string {
|
||||
return fmt.Sprintf("Copy <%s> %s", relType(v.Type(), v.Parent().pkg()), relName(v.X, v))
|
||||
}
|
||||
|
||||
func printConv(prefix string, v, x Value) string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("%s <%s> %s",
|
||||
prefix,
|
||||
relType(v.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("ChangeInterface", v, v.X) }
|
||||
func (v *SliceToArrayPointer) String() string { return printConv("SliceToArrayPointer", v, v.X) }
|
||||
func (v *SliceToArray) String() string { return printConv("SliceToArray", v, v.X) }
|
||||
func (v *MakeInterface) String() string { return printConv("MakeInterface", v, v.X) }
|
||||
|
||||
func (v *MakeClosure) String() string {
|
||||
from := v.Parent().pkg()
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, "MakeClosure <%s> %s", relType(v.Type(), from), relName(v.Fn, v))
|
||||
if v.Bindings != nil {
|
||||
for _, c := range v.Bindings {
|
||||
b.WriteString(" ")
|
||||
b.WriteString(relName(c, v))
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (v *MakeSlice) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("MakeSlice <%s> %s %s",
|
||||
relType(v.Type(), from),
|
||||
relName(v.Len, v),
|
||||
relName(v.Cap, v))
|
||||
}
|
||||
|
||||
func (v *Slice) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("Slice <%s> %s %s %s %s",
|
||||
relType(v.Type(), from), relName(v.X, v), relName(v.Low, v), relName(v.High, v), relName(v.Max, v))
|
||||
}
|
||||
|
||||
func (v *MakeMap) String() string {
|
||||
res := ""
|
||||
if v.Reserve != nil {
|
||||
res = relName(v.Reserve, v)
|
||||
}
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("MakeMap <%s> %s", relType(v.Type(), from), res)
|
||||
}
|
||||
|
||||
func (v *MakeChan) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("MakeChan <%s> %s", relType(v.Type(), from), relName(v.Size, v))
|
||||
}
|
||||
|
||||
func (v *FieldAddr) String() string {
|
||||
from := v.Parent().pkg()
|
||||
// v.X.Type() might be a pointer to a type parameter whose core type is a pointer to a struct
|
||||
st := deref(typeutil.CoreType(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("FieldAddr <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v))
|
||||
}
|
||||
|
||||
func (v *Field) String() string {
|
||||
st := typeutil.CoreType(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()
|
||||
}
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("Field <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v))
|
||||
}
|
||||
|
||||
func (v *IndexAddr) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("IndexAddr <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v))
|
||||
}
|
||||
|
||||
func (v *Index) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("Index <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v))
|
||||
}
|
||||
|
||||
func (v *MapLookup) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("MapLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v))
|
||||
}
|
||||
|
||||
func (v *StringLookup) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("StringLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v))
|
||||
}
|
||||
|
||||
func (v *Range) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("Range <%s> %s", relType(v.Type(), from), relName(v.X, v))
|
||||
}
|
||||
|
||||
func (v *Next) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("Next <%s> %s", relType(v.Type(), from), relName(v.Iter, v))
|
||||
}
|
||||
|
||||
func (v *TypeAssert) String() string {
|
||||
from := v.Parent().pkg()
|
||||
return fmt.Sprintf("TypeAssert <%s> %s", relType(v.Type(), from), relName(v.X, v))
|
||||
}
|
||||
|
||||
func (v *Extract) String() string {
|
||||
from := v.Parent().pkg()
|
||||
name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name()
|
||||
return fmt.Sprintf("Extract <%s> [%d] (%s) %s", relType(v.Type(), from), v.Index, name, relName(v.Tuple, v))
|
||||
}
|
||||
|
||||
func (s *Jump) String() string {
|
||||
// Be robust against malformed CFG.
|
||||
block := -1
|
||||
if s.block != nil && len(s.block.Succs) == 1 {
|
||||
block = s.block.Succs[0].Index
|
||||
}
|
||||
str := fmt.Sprintf("Jump → b%d", block)
|
||||
if s.Comment() != "" {
|
||||
str = fmt.Sprintf("%s # %s", str, s.Comment())
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func (s *Unreachable) 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("Unreachable → b%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 → b%d b%d", relName(s.Cond, s), tblock, fblock)
|
||||
}
|
||||
|
||||
func (s *ConstantSwitch) String() string {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, "ConstantSwitch %s", relName(s.Tag, s))
|
||||
for _, cond := range s.Conds {
|
||||
fmt.Fprintf(&b, " %s", relName(cond, s))
|
||||
}
|
||||
fmt.Fprint(&b, " →")
|
||||
for _, succ := range s.block.Succs {
|
||||
fmt.Fprintf(&b, " b%d", succ.Index)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (v *CompositeValue) String() string {
|
||||
var b bytes.Buffer
|
||||
from := v.Parent().pkg()
|
||||
fmt.Fprintf(&b, "CompositeValue <%s>", relType(v.Type(), from))
|
||||
if v.NumSet >= len(v.Values) {
|
||||
// All values provided
|
||||
fmt.Fprint(&b, " [all]")
|
||||
} else if v.Bitmap.BitLen() == 0 {
|
||||
// No values provided
|
||||
fmt.Fprint(&b, " [none]")
|
||||
} else {
|
||||
// Some values provided
|
||||
bits := fmt.Appendf(nil, "%0*b", len(v.Values), &v.Bitmap)
|
||||
for i := 0; i < len(bits)/2; i++ {
|
||||
o := len(bits) - 1 - i
|
||||
bits[i], bits[o] = bits[o], bits[i]
|
||||
}
|
||||
fmt.Fprintf(&b, " [%s]", bits)
|
||||
}
|
||||
for _, vv := range v.Values {
|
||||
fmt.Fprintf(&b, " %s", relName(vv, v))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (s *TypeSwitch) String() string {
|
||||
from := s.Parent().pkg()
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, "TypeSwitch <%s> %s", relType(s.typ, from), relName(s.Tag, s))
|
||||
for _, cond := range s.Conds {
|
||||
fmt.Fprintf(&b, " %q", relType(cond, s.block.parent.pkg()))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (s *Go) String() string {
|
||||
return printCall(&s.Call, "Go", s)
|
||||
}
|
||||
|
||||
func (s *Panic) 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("Panic %s → b%d", relName(s.X, s), block)
|
||||
}
|
||||
|
||||
func (s *Return) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("Return")
|
||||
for _, r := range s.Results {
|
||||
b.WriteString(" ")
|
||||
b.WriteString(relName(r, s))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (*RunDefers) String() string {
|
||||
return "RunDefers"
|
||||
}
|
||||
|
||||
func (s *Send) String() string {
|
||||
return fmt.Sprintf("Send %s %s", relName(s.Chan, s), relName(s.X, s))
|
||||
}
|
||||
|
||||
func (recv *Recv) String() string {
|
||||
from := recv.Parent().pkg()
|
||||
return fmt.Sprintf("Recv <%s> %s", relType(recv.Type(), from), relName(recv.Chan, recv))
|
||||
}
|
||||
|
||||
func (s *Defer) String() string {
|
||||
prefix := "Defer "
|
||||
if s._DeferStack != nil {
|
||||
prefix += "[" + relName(s._DeferStack, s) + "] "
|
||||
}
|
||||
c := printCall(&s.Call, prefix, s)
|
||||
return c
|
||||
}
|
||||
|
||||
func (s *Select) String() string {
|
||||
var b bytes.Buffer
|
||||
for i, st := range s.States {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
if st.Dir == types.RecvOnly {
|
||||
b.WriteString("<-")
|
||||
b.WriteString(relName(st.Chan, s))
|
||||
} else {
|
||||
b.WriteString(relName(st.Chan, s))
|
||||
b.WriteString("<-")
|
||||
b.WriteString(relName(st.Send, s))
|
||||
}
|
||||
}
|
||||
non := ""
|
||||
if !s.Blocking {
|
||||
non = "Non"
|
||||
}
|
||||
from := s.Parent().pkg()
|
||||
return fmt.Sprintf("Select%sBlocking <%s> [%s]", non, relType(s.Type(), from), b.String())
|
||||
}
|
||||
|
||||
func (s *Store) String() string {
|
||||
return fmt.Sprintf("Store {%s} %s %s",
|
||||
s.Val.Type(), relName(s.Addr, s), relName(s.Val, s))
|
||||
}
|
||||
|
||||
func (s *BlankStore) String() string {
|
||||
return fmt.Sprintf("BlankStore %s", relName(s.Val, s))
|
||||
}
|
||||
|
||||
func (s *MapUpdate) String() string {
|
||||
return fmt.Sprintf("MapUpdate %s %s %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s))
|
||||
}
|
||||
|
||||
func (s *DebugRef) String() string {
|
||||
p := s.Parent().Prog.Fset.Position(s.Pos())
|
||||
var descr any
|
||||
if s.object != nil {
|
||||
descr = s.object // e.g. "var x int"
|
||||
} else {
|
||||
descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr"
|
||||
}
|
||||
var addr string
|
||||
if s.IsAddr {
|
||||
addr = "address of "
|
||||
}
|
||||
return fmt.Sprintf("; %s%s @ %d:%d is %s", addr, descr, p.Line, p.Column, s.X.Name())
|
||||
}
|
||||
|
||||
func (p *Package) String() string {
|
||||
return "package " + p.Pkg.Path()
|
||||
}
|
||||
|
||||
var _ io.WriterTo = (*Package)(nil) // *Package implements io.Writer
|
||||
|
||||
func (p *Package) WriteTo(w io.Writer) (int64, error) {
|
||||
var buf bytes.Buffer
|
||||
WritePackage(&buf, p)
|
||||
n, err := w.Write(buf.Bytes())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// WritePackage writes to buf a human-readable summary of p.
|
||||
func WritePackage(buf *bytes.Buffer, p *Package) {
|
||||
fmt.Fprintf(buf, "%s:\n", p)
|
||||
|
||||
var names []string
|
||||
maxname := 0
|
||||
for name := range p.Members {
|
||||
if l := len(name); l > maxname {
|
||||
maxname = l
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
from := p.Pkg
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
switch mem := p.Members[name].(type) {
|
||||
case *NamedConst:
|
||||
fmt.Fprintf(buf, " const %-*s %s = %s\n",
|
||||
maxname, name, mem.Name(), mem.Value.RelString(from))
|
||||
|
||||
case *Function:
|
||||
fmt.Fprintf(buf, " func %-*s %s\n",
|
||||
maxname, name, relType(mem.Type(), from))
|
||||
|
||||
case *Type:
|
||||
fmt.Fprintf(buf, " type %-*s %s\n",
|
||||
maxname, name, relType(mem.Type().Underlying(), from))
|
||||
for _, meth := range typeutil.IntuitiveMethodSet(mem.Type(), &p.Prog.MethodSets) {
|
||||
fmt.Fprintf(buf, " %s\n", types.SelectionString(meth, types.RelativeTo(from)))
|
||||
}
|
||||
|
||||
case *Global:
|
||||
fmt.Fprintf(buf, " var %-*s %s\n",
|
||||
maxname, name, relType(mem.Type().(*types.Pointer).Elem(), from))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(buf, "\n")
|
||||
}
|
||||
|
||||
func (v *MultiConvert) String() string {
|
||||
from := v.Parent().Pkg.Pkg
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(printConv("MultiConvert", v, v.X))
|
||||
b.WriteString(" [")
|
||||
for i, s := range v.from.Terms {
|
||||
for j, d := range v.to.Terms {
|
||||
if i != 0 || j != 0 {
|
||||
b.WriteString(" | ")
|
||||
}
|
||||
fmt.Fprintf(&b, "%s -> %s", relTerm(s, from), relTerm(d, from))
|
||||
}
|
||||
}
|
||||
b.WriteString("]")
|
||||
return b.String()
|
||||
}
|
||||
546
vendor/honnef.co/go/tools/go/ir/sanity.go
vendored
Normal file
546
vendor/honnef.co/go/tools/go/ir/sanity.go
vendored
Normal file
@@ -0,0 +1,546 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// An optional pass for sanity-checking invariants of the IR representation.
|
||||
// Currently it checks CFG invariants but little at the instruction level.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
type sanity struct {
|
||||
reporter io.Writer
|
||||
fn *Function
|
||||
block *BasicBlock
|
||||
instrs map[Instruction]struct{}
|
||||
insane bool
|
||||
}
|
||||
|
||||
// sanityCheck performs integrity checking of the IR representation
|
||||
// of the function fn and returns true if it was valid. Diagnostics
|
||||
// are written to reporter if non-nil, os.Stderr otherwise. Some
|
||||
// diagnostics are only warnings and do not imply a negative result.
|
||||
//
|
||||
// Sanity-checking is intended to facilitate the debugging of code
|
||||
// transformation passes.
|
||||
func sanityCheck(fn *Function, reporter io.Writer) bool {
|
||||
if reporter == nil {
|
||||
reporter = os.Stderr
|
||||
}
|
||||
return (&sanity{reporter: reporter}).checkFunction(fn)
|
||||
}
|
||||
|
||||
// mustSanityCheck is like sanityCheck but panics instead of returning
|
||||
// a negative result.
|
||||
func mustSanityCheck(fn *Function, reporter io.Writer) {
|
||||
if !sanityCheck(fn, reporter) {
|
||||
fn.WriteTo(os.Stderr)
|
||||
panic("SanityCheck failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) diagnostic(prefix, format string, args ...any) {
|
||||
fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn)
|
||||
if s.block != nil {
|
||||
fmt.Fprintf(s.reporter, ", block %s", s.block)
|
||||
}
|
||||
io.WriteString(s.reporter, ": ")
|
||||
fmt.Fprintf(s.reporter, format, args...)
|
||||
io.WriteString(s.reporter, "\n")
|
||||
}
|
||||
|
||||
func (s *sanity) errorf(format string, args ...any) {
|
||||
s.insane = true
|
||||
s.diagnostic("Error", format, args...)
|
||||
}
|
||||
|
||||
func (s *sanity) warnf(format string, args ...any) {
|
||||
s.diagnostic("Warning", format, args...)
|
||||
}
|
||||
|
||||
// findDuplicate returns an arbitrary basic block that appeared more
|
||||
// than once in blocks, or nil if all were unique.
|
||||
func findDuplicate(blocks []*BasicBlock) *BasicBlock {
|
||||
if len(blocks) < 2 {
|
||||
return nil
|
||||
}
|
||||
if blocks[0] == blocks[1] {
|
||||
return blocks[0]
|
||||
}
|
||||
// Slow path:
|
||||
m := make(map[*BasicBlock]bool)
|
||||
for _, b := range blocks {
|
||||
if m[b] {
|
||||
return b
|
||||
}
|
||||
m[b] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sanity) checkInstr(idx int, instr Instruction) {
|
||||
switch instr := instr.(type) {
|
||||
case *If, *Jump, *Return, *Panic, *Unreachable, *ConstantSwitch:
|
||||
s.errorf("control flow instruction not at end of block")
|
||||
case *Sigma:
|
||||
if idx > 0 {
|
||||
prev := s.block.Instrs[idx-1]
|
||||
if _, ok := prev.(*Sigma); !ok {
|
||||
s.errorf("Sigma instruction follows a non-Sigma: %T", prev)
|
||||
}
|
||||
}
|
||||
case *Phi:
|
||||
if idx == 0 {
|
||||
// It suffices to apply this check to just the first phi node.
|
||||
if dup := findDuplicate(s.block.Preds); dup != nil {
|
||||
s.errorf("phi node in block with duplicate predecessor %s", dup)
|
||||
}
|
||||
} else {
|
||||
prev := s.block.Instrs[idx-1]
|
||||
switch prev.(type) {
|
||||
case *Phi, *Sigma:
|
||||
default:
|
||||
s.errorf("Phi instruction follows a non-Phi, non-Sigma: %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 && !slices.Contains(s.fn.Locals, instr) {
|
||||
s.errorf("local alloc %s = %s does not appear in Function.Locals", instr.Name(), instr)
|
||||
}
|
||||
|
||||
case *BinOp:
|
||||
case *Call:
|
||||
case *ChangeInterface:
|
||||
case *ChangeType:
|
||||
case *SliceToArrayPointer:
|
||||
case *SliceToArray:
|
||||
case *Convert:
|
||||
tsetInstrX := typeutil.NewTypeSet(instr.X.Type().Underlying())
|
||||
tsetInstr := typeutil.NewTypeSet(instr.Type().Underlying())
|
||||
ok1 := tsetInstr.Any(func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Basic); return ok })
|
||||
ok2 := tsetInstrX.Any(func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Basic); return ok })
|
||||
if !ok1 && !ok2 {
|
||||
s.errorf("convert %s -> %s: at least one type set must contain basic type", instr.X.Type(), instr.Type())
|
||||
}
|
||||
|
||||
case *MultiConvert:
|
||||
case *Defer:
|
||||
case *Extract:
|
||||
case *Field:
|
||||
case *FieldAddr:
|
||||
case *Go:
|
||||
case *Index:
|
||||
case *IndexAddr:
|
||||
case *MapLookup:
|
||||
case *StringLookup:
|
||||
case *MakeChan:
|
||||
case *MakeClosure:
|
||||
numFree := len(instr.Fn.(*Function).FreeVars)
|
||||
numBind := len(instr.Bindings)
|
||||
if numFree != numBind {
|
||||
s.errorf("MakeClosure has %d Bindings for function %s with %d free vars",
|
||||
numBind, instr.Fn, numFree)
|
||||
|
||||
}
|
||||
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 *Load:
|
||||
case *Parameter:
|
||||
case *Const:
|
||||
case *AggregateConst:
|
||||
case *ArrayConst:
|
||||
case *GenericConst:
|
||||
case *Recv:
|
||||
case *TypeSwitch:
|
||||
case *CompositeValue:
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown instruction type: %T", instr))
|
||||
}
|
||||
|
||||
if call, ok := instr.(CallInstruction); ok {
|
||||
if call.Common().Signature() == nil {
|
||||
s.errorf("nil signature: %s", call)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that value-defining instructions have valid types
|
||||
// and a valid referrer list.
|
||||
if v, ok := instr.(Value); ok {
|
||||
t := v.Type()
|
||||
if t == nil {
|
||||
s.errorf("no type: %s = %s", v.Name(), v)
|
||||
} else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
|
||||
if _, ok := v.(*Const); !ok {
|
||||
s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t)
|
||||
}
|
||||
}
|
||||
s.checkReferrerList(v)
|
||||
}
|
||||
|
||||
// Untyped constants are legal as instruction Operands(),
|
||||
// for example:
|
||||
// _ = "foo"[0]
|
||||
// or:
|
||||
// if wordsize==64 {...}
|
||||
|
||||
// All other non-Instruction Values can be found via their
|
||||
// enclosing Function or Package.
|
||||
}
|
||||
|
||||
func (s *sanity) checkFinalInstr(instr Instruction) {
|
||||
switch instr := instr.(type) {
|
||||
case *If:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 2 {
|
||||
s.errorf("If-terminated block has %d successors; expected 2", nsuccs)
|
||||
return
|
||||
}
|
||||
if s.block.Succs[0] == s.block.Succs[1] {
|
||||
s.errorf("If-instruction has same True, False target blocks: %s", s.block.Succs[0])
|
||||
return
|
||||
}
|
||||
|
||||
case *Jump:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 1 {
|
||||
s.errorf("Jump-terminated block has %d successors; expected 1", nsuccs)
|
||||
return
|
||||
}
|
||||
|
||||
case *Return:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 0 {
|
||||
s.errorf("Return-terminated block has %d successors; expected none", nsuccs)
|
||||
return
|
||||
}
|
||||
if na, nf := len(instr.Results), s.fn.Signature.Results().Len(); nf != na {
|
||||
s.errorf("%d-ary return in %d-ary function", na, nf)
|
||||
}
|
||||
|
||||
case *Panic:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 1 {
|
||||
s.errorf("Panic-terminated block has %d successors; expected one", nsuccs)
|
||||
return
|
||||
}
|
||||
|
||||
case *Unreachable:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 1 {
|
||||
s.errorf("Unreachable-terminated block has %d successors; expected one", nsuccs)
|
||||
return
|
||||
}
|
||||
|
||||
case *ConstantSwitch:
|
||||
|
||||
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, the exit block may be unreachable.)
|
||||
if index > 1 && len(b.Preds) == 0 {
|
||||
s.warnf("unreachable block")
|
||||
if b.Instrs == nil {
|
||||
// Since this block is about to be pruned,
|
||||
// tolerating transient problems in it
|
||||
// simplifies other optimizations.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check predecessor and successor relations are dual,
|
||||
// and that all blocks in CFG belong to same function.
|
||||
for _, a := range b.Preds {
|
||||
if !slices.Contains(a.Succs, b) {
|
||||
s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs)
|
||||
}
|
||||
if a.parent != s.fn {
|
||||
s.errorf("predecessor %s belongs to different function %s", a, a.parent)
|
||||
}
|
||||
}
|
||||
for _, c := range b.Succs {
|
||||
if !slices.Contains(c.Preds, b) {
|
||||
s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds)
|
||||
}
|
||||
if c.parent != s.fn {
|
||||
s.errorf("successor %s belongs to different function %s", c, c.parent)
|
||||
}
|
||||
}
|
||||
|
||||
// Check each instruction is sane.
|
||||
n := len(b.Instrs)
|
||||
if n == 0 {
|
||||
s.errorf("basic block contains no instructions")
|
||||
}
|
||||
var rands [10]*Value // reuse storage
|
||||
for j, instr := range b.Instrs {
|
||||
if instr == nil {
|
||||
s.errorf("nil instruction at index %d", j)
|
||||
continue
|
||||
}
|
||||
if b2 := instr.Block(); b2 == nil {
|
||||
s.errorf("nil Block() for instruction at index %d", j)
|
||||
continue
|
||||
} else if b2 != b {
|
||||
s.errorf("wrong Block() (%s) for instruction at index %d ", b2, j)
|
||||
continue
|
||||
}
|
||||
if j < n-1 {
|
||||
s.checkInstr(j, instr)
|
||||
} else {
|
||||
s.checkFinalInstr(instr)
|
||||
}
|
||||
|
||||
// Check Instruction.Operands.
|
||||
operands:
|
||||
for i, op := range instr.Operands(rands[:0]) {
|
||||
if op == nil {
|
||||
s.errorf("nil operand pointer %d of %s", i, instr)
|
||||
continue
|
||||
}
|
||||
val := *op
|
||||
if val == nil {
|
||||
continue // a nil operand is ok
|
||||
}
|
||||
|
||||
// Check that "untyped" types only appear on constant operands.
|
||||
if _, ok := (*op).(*Const); !ok {
|
||||
if basic, ok := types.Unalias((*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 {
|
||||
if val, ok := ref.(Value); ok {
|
||||
s.errorf("%s.Referrers()[%d] = %s = %s is not an instruction belonging to this function", v.Name(), i, val.Name(), val)
|
||||
} else {
|
||||
s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) 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")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = fn.String() // must not crash
|
||||
_ = fn.RelString(fn.pkg()) // must not crash
|
||||
WriteFunction(&buf, fn) // must not crash
|
||||
|
||||
// All functions have a package, except delegates (which are
|
||||
// shared across packages, or duplicated as weak symbols in a
|
||||
// separate-compilation model), and error.Error.
|
||||
if fn.Pkg == nil {
|
||||
switch fn.Synthetic {
|
||||
case SyntheticWrapper, SyntheticBound, SyntheticThunk, SyntheticGeneric:
|
||||
default:
|
||||
if !strings.HasSuffix(fn.name, "Error") {
|
||||
s.errorf("nil Pkg")
|
||||
}
|
||||
}
|
||||
}
|
||||
if syn, src := fn.Synthetic == 0, fn.source != nil; src != syn {
|
||||
if _, ok := fn.source.(*ast.RangeStmt); !ok || fn.Synthetic != SyntheticRangeOverFuncYield {
|
||||
// Only range-over-func yield functions are synthetic and have syntax
|
||||
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)
|
||||
}
|
||||
// Check common suffix of Signature and Params match type.
|
||||
if sig := fn.Signature; sig != nil {
|
||||
j := i - len(fn.Params) + sig.Params().Len() // index within sig.Params
|
||||
if j < 0 {
|
||||
continue
|
||||
}
|
||||
if !types.Identical(p.Type(), sig.Params().At(j).Type()) {
|
||||
s.errorf("Param %s at index %d has wrong type (%s, versus %s in Signature)", p.Name(), i, p.Type(), sig.Params().At(j).Type())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
s.checkReferrerList(p)
|
||||
}
|
||||
for i, fv := range fn.FreeVars {
|
||||
if fv.Parent() != fn {
|
||||
s.errorf("FreeVar %s at index %d has wrong parent", fv.Name(), i)
|
||||
}
|
||||
s.checkReferrerList(fv)
|
||||
}
|
||||
|
||||
if fn.Blocks != nil && len(fn.Blocks) == 0 {
|
||||
// Function _had_ blocks (so it's not external) but
|
||||
// they were "optimized" away, even the entry block.
|
||||
s.errorf("Blocks slice is non-nil but empty")
|
||||
}
|
||||
for i, b := range fn.Blocks {
|
||||
if b == nil {
|
||||
s.warnf("nil *BasicBlock at f.Blocks[%d]", i)
|
||||
continue
|
||||
}
|
||||
s.checkBlock(b, i)
|
||||
}
|
||||
|
||||
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 ir.Function ("init#%d").
|
||||
} else {
|
||||
panic(fmt.Sprintf("%s: %T.Object().Name() = %s, want %s",
|
||||
pkg.Pkg.Path(), mem, obj.Name(), name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
263
vendor/honnef.co/go/tools/go/ir/source.go
vendored
Normal file
263
vendor/honnef.co/go/tools/go/ir/source.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 ir
|
||||
|
||||
// This file defines utilities for working with source positions
|
||||
// or source-level named entities ("objects").
|
||||
|
||||
// TODO(adonovan): test that {Value,Instruction}.Pos() positions match
|
||||
// the originating syntax, as specified.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// EnclosingFunction returns the function that contains the syntax
|
||||
// node denoted by path.
|
||||
//
|
||||
// Syntax associated with package-level variable specifications is
|
||||
// enclosed by the package's init() function.
|
||||
//
|
||||
// Returns nil if not found; reasons might include:
|
||||
// - the node is not enclosed by any function.
|
||||
// - the node is within an anonymous function (FuncLit) and
|
||||
// its IR function has not been created yet
|
||||
// (pkg.Build() has not yet been called).
|
||||
func EnclosingFunction(pkg *Package, path []ast.Node) *Function {
|
||||
// Start with package-level function...
|
||||
fn := findEnclosingPackageLevelFunction(pkg, path)
|
||||
if fn == nil {
|
||||
return nil // not in any function
|
||||
}
|
||||
|
||||
// ...then walk down the nested anonymous functions.
|
||||
n := len(path)
|
||||
outer:
|
||||
for i := range path {
|
||||
if lit, ok := path[n-1-i].(*ast.FuncLit); ok {
|
||||
for _, anon := range fn.AnonFuncs {
|
||||
if anon.Pos() == lit.Type.Func {
|
||||
fn = anon
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
// IR function not found:
|
||||
// - package not yet built, or maybe
|
||||
// - builder skipped FuncLit in dead block
|
||||
// (in principle; but currently the Builder
|
||||
// generates even dead FuncLits).
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// HasEnclosingFunction returns true if the AST node denoted by path
|
||||
// is contained within the declaration of some function or
|
||||
// package-level variable.
|
||||
//
|
||||
// Unlike EnclosingFunction, the behaviour of this function does not
|
||||
// depend on whether IR code for pkg has been built, so it can be
|
||||
// used to quickly reject check inputs that will cause
|
||||
// EnclosingFunction to fail, prior to IR building.
|
||||
func HasEnclosingFunction(pkg *Package, path []ast.Node) bool {
|
||||
return findEnclosingPackageLevelFunction(pkg, path) != nil
|
||||
}
|
||||
|
||||
// findEnclosingPackageLevelFunction returns the Function
|
||||
// corresponding to the package-level function enclosing path.
|
||||
func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function {
|
||||
if n := len(path); n >= 2 { // [... {Gen,Func}Decl File]
|
||||
switch decl := path[n-2].(type) {
|
||||
case *ast.GenDecl:
|
||||
if decl.Tok == token.VAR && n >= 3 {
|
||||
// Package-level 'var' initializer.
|
||||
return pkg.init
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
// Declared function/method.
|
||||
fn := findNamedFunc(pkg, decl.Pos())
|
||||
if fn == nil && decl.Recv == nil && decl.Name.Name == "init" {
|
||||
// Hack: return non-nil when IR is not yet
|
||||
// built so that HasEnclosingFunction works.
|
||||
return pkg.init
|
||||
}
|
||||
return fn
|
||||
}
|
||||
}
|
||||
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 {
|
||||
for _, fn := range pkg.Functions {
|
||||
if fn.Pos() == pos {
|
||||
return fn
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueForExpr returns the IR Value that corresponds to non-constant
|
||||
// expression e.
|
||||
//
|
||||
// It returns nil if no value was found, e.g.
|
||||
// - the expression is not lexically contained within f;
|
||||
// - f was not built with debug information; or
|
||||
// - e is a constant expression. (For efficiency, no debug
|
||||
// information is stored for constants. Use
|
||||
// go/types.Info.Types[e].Value instead.)
|
||||
// - e is a reference to nil or a built-in function.
|
||||
// - the value was optimised away.
|
||||
//
|
||||
// If e is an addressable expression used in an lvalue context,
|
||||
// value is the address denoted by e, and isAddr is true.
|
||||
//
|
||||
// The types of e (or &e, if isAddr) and the result are equal
|
||||
// (modulo "untyped" bools resulting from comparisons).
|
||||
//
|
||||
// (Tip: to find the ir.Value given a source position, use
|
||||
// astutil.PathEnclosingInterval to locate the ast.Node, then
|
||||
// EnclosingFunction to locate the Function, then ValueForExpr to find
|
||||
// the ir.Value.)
|
||||
func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) {
|
||||
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 IR Package corresponding to the specified
|
||||
// type-checker package object.
|
||||
// It returns nil if no such IR package has been created.
|
||||
func (prog *Program) Package(obj *types.Package) *Package {
|
||||
return prog.packages[obj]
|
||||
}
|
||||
|
||||
// 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 {
|
||||
obj = obj.Origin()
|
||||
fn, _ := prog.packageLevelValue(obj).(*Function)
|
||||
return fn
|
||||
}
|
||||
|
||||
// ConstValue returns the IR Value denoted by the source-level named
|
||||
// constant obj.
|
||||
func (prog *Program) ConstValue(obj *types.Const) *Const {
|
||||
// TODO(adonovan): opt: share (don't reallocate)
|
||||
// Consts for const objects and constant ast.Exprs.
|
||||
|
||||
// Universal constant? {true,false,nil}
|
||||
if obj.Parent() == types.Universe {
|
||||
return NewConst(obj.Val(), obj.Type(), nil)
|
||||
}
|
||||
// Package-level named constant?
|
||||
if v := prog.packageLevelValue(obj); v != nil {
|
||||
return v.(*Const)
|
||||
}
|
||||
return NewConst(obj.Val(), obj.Type(), nil)
|
||||
}
|
||||
|
||||
// VarValue returns the IR Value that corresponds to a specific
|
||||
// identifier denoting the source-level named variable obj.
|
||||
//
|
||||
// VarValue returns nil if a local variable was not found, perhaps
|
||||
// because its package was not built, the debug information was not
|
||||
// requested during IR construction, or the value was optimized away.
|
||||
//
|
||||
// ref is the path to an ast.Ident (e.g. from PathEnclosingInterval),
|
||||
// and that ident must resolve to obj.
|
||||
//
|
||||
// pkg is the package enclosing the reference. (A reference to a var
|
||||
// always occurs within a function, so we need to know where to find it.)
|
||||
//
|
||||
// If the identifier is a field selector and its base expression is
|
||||
// non-addressable, then VarValue returns the value of that field.
|
||||
// For example:
|
||||
//
|
||||
// func f() struct {x int}
|
||||
// f().x // VarValue(x) returns a *Field instruction of type int
|
||||
//
|
||||
// All other identifiers denote addressable locations (variables).
|
||||
// For them, VarValue may return either the variable's address or its
|
||||
// value, even when the expression is evaluated only for its value; the
|
||||
// situation is reported by isAddr, the second component of the result.
|
||||
//
|
||||
// If !isAddr, the returned value is the one associated with the
|
||||
// specific identifier. For example,
|
||||
//
|
||||
// var x int // VarValue(x) returns Const 0 here
|
||||
// x = 1 // VarValue(x) returns Const 1 here
|
||||
//
|
||||
// It is not specified whether the value or the address is returned in
|
||||
// any particular case, as it may depend upon optimizations performed
|
||||
// during IR code generation, such as registerization, constant
|
||||
// folding, avoidance of materialization of subexpressions, etc.
|
||||
func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) {
|
||||
// All references to a var are local to some function, possibly init.
|
||||
fn := EnclosingFunction(pkg, ref)
|
||||
if fn == nil {
|
||||
return // e.g. def of struct field; IR not built?
|
||||
}
|
||||
|
||||
id := ref[0].(*ast.Ident)
|
||||
|
||||
// Defining ident of a parameter?
|
||||
if id.Pos() == obj.Pos() {
|
||||
for _, param := range fn.Params {
|
||||
if param.Object() == obj {
|
||||
return param, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Other ident?
|
||||
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
|
||||
}
|
||||
2150
vendor/honnef.co/go/tools/go/ir/ssa.go
vendored
Normal file
2150
vendor/honnef.co/go/tools/go/ir/ssa.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
162
vendor/honnef.co/go/tools/go/ir/util.go
vendored
Normal file
162
vendor/honnef.co/go/tools/go/ir/util.go
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// This file defines a number of miscellaneous utility functions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"honnef.co/go/tools/go/ast/astutil"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
|
||||
"golang.org/x/exp/typeparams"
|
||||
)
|
||||
|
||||
//// 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,
|
||||
// and for type parameters whose core type is a pointer.
|
||||
func isPointer(typ types.Type) bool {
|
||||
if ctyp := typeutil.CoreType(typ); ctyp != nil {
|
||||
_, ok := ctyp.(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
_, ok := typ.Underlying().(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
|
||||
// deref returns a pointer's element type; otherwise it returns typ.
|
||||
func deref(typ types.Type) types.Type {
|
||||
orig := typ
|
||||
typ = types.Unalias(typ)
|
||||
|
||||
if t, ok := typ.(*types.TypeParam); ok {
|
||||
if ctyp := typeutil.CoreType(t); ctyp != nil {
|
||||
// This can happen, for example, with len(T) where T is a
|
||||
// type parameter whose core type is a pointer to array.
|
||||
typ = ctyp
|
||||
}
|
||||
}
|
||||
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||
return p.Elem()
|
||||
}
|
||||
return orig
|
||||
}
|
||||
|
||||
// recvType returns the receiver type of method obj.
|
||||
func recvType(obj *types.Func) types.Type {
|
||||
return obj.Type().(*types.Signature).Recv().Type()
|
||||
}
|
||||
|
||||
// logStack prints the formatted "start" message to stderr and
|
||||
// returns a closure that prints the corresponding "end" message.
|
||||
// Call using 'defer logStack(...)()' to show builder stack on panic.
|
||||
// Don't forget trailing parens!
|
||||
func logStack(format string, args ...any) func() {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
io.WriteString(os.Stderr, msg)
|
||||
io.WriteString(os.Stderr, "\n")
|
||||
return func() {
|
||||
io.WriteString(os.Stderr, msg)
|
||||
io.WriteString(os.Stderr, " end\n")
|
||||
}
|
||||
}
|
||||
|
||||
// newVar creates a 'var' for use in a types.Tuple.
|
||||
func newVar(name string, typ types.Type) *types.Var {
|
||||
return types.NewParam(token.NoPos, nil, name, typ)
|
||||
}
|
||||
|
||||
// anonVar creates an anonymous 'var' for use in a types.Tuple.
|
||||
func anonVar(typ types.Type) *types.Var {
|
||||
return newVar("", typ)
|
||||
}
|
||||
|
||||
var lenResults = types.NewTuple(anonVar(tInt))
|
||||
|
||||
// makeLen returns the len builtin specialized to type func(T)int.
|
||||
func makeLen(T types.Type) *Builtin {
|
||||
lenParams := types.NewTuple(anonVar(T))
|
||||
return &Builtin{
|
||||
name: "len",
|
||||
sig: types.NewSignatureType(nil, nil, nil, lenParams, lenResults, false),
|
||||
}
|
||||
}
|
||||
|
||||
type StackMap struct {
|
||||
m []map[Value]Value
|
||||
}
|
||||
|
||||
func (m *StackMap) Push() {
|
||||
m.m = append(m.m, map[Value]Value{})
|
||||
}
|
||||
|
||||
func (m *StackMap) Pop() {
|
||||
m.m = m.m[:len(m.m)-1]
|
||||
}
|
||||
|
||||
func (m *StackMap) Get(key Value) (Value, bool) {
|
||||
for i := len(m.m) - 1; i >= 0; i-- {
|
||||
if v, ok := m.m[i][key]; ok {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (m *StackMap) Set(k Value, v Value) {
|
||||
m.m[len(m.m)-1][k] = v
|
||||
}
|
||||
|
||||
// Unwrap recursively unwraps Sigma and Copy nodes.
|
||||
func Unwrap(v Value) Value {
|
||||
for {
|
||||
switch vv := v.(type) {
|
||||
case *Sigma:
|
||||
v = vv.X
|
||||
case *Copy:
|
||||
v = vv.X
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assert(x bool) {
|
||||
if !x {
|
||||
panic("failed assertion")
|
||||
}
|
||||
}
|
||||
|
||||
// BlockMap is a mapping from basic blocks (identified by their indices) to values.
|
||||
type BlockMap[T any] []T
|
||||
|
||||
// isBasic reports whether t is a basic type.
|
||||
func isBasic(t types.Type) bool {
|
||||
_, ok := t.(*types.Basic)
|
||||
return ok
|
||||
}
|
||||
|
||||
// isNonTypeParamInterface reports whether t is an interface type but not a type parameter.
|
||||
func isNonTypeParamInterface(t types.Type) bool {
|
||||
return !typeparams.IsTypeParam(t) && types.IsInterface(t)
|
||||
}
|
||||
342
vendor/honnef.co/go/tools/go/ir/wrappers.go
vendored
Normal file
342
vendor/honnef.co/go/tools/go/ir/wrappers.go
vendored
Normal file
@@ -0,0 +1,342 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ir
|
||||
|
||||
// This file defines synthesis of Functions that delegate to declared
|
||||
// methods; they come in three kinds:
|
||||
//
|
||||
// (1) wrappers: methods that wrap declared methods, performing
|
||||
// implicit pointer indirections and embedded field selections.
|
||||
//
|
||||
// (2) thunks: funcs that wrap declared methods. Like wrappers,
|
||||
// thunks perform indirections and field selections. The thunk's
|
||||
// first parameter is used as the receiver for the method call.
|
||||
//
|
||||
// (3) bounds: funcs that wrap declared methods. The bound's sole
|
||||
// free variable, supplied by a closure, is used as the receiver
|
||||
// for the method call. No indirections or field selections are
|
||||
// performed since they can be done before the call.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// -- wrappers -----------------------------------------------------------
|
||||
|
||||
// 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 Synthetic
|
||||
var start int // first regular param
|
||||
if sel.Kind() == types.MethodExpr {
|
||||
name += "$thunk"
|
||||
description = SyntheticThunk
|
||||
recv = sig.Params().At(0)
|
||||
start = 1
|
||||
} else {
|
||||
description = SyntheticWrapper
|
||||
recv = sig.Recv()
|
||||
}
|
||||
|
||||
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,
|
||||
functionBody: new(functionBody),
|
||||
}
|
||||
fn.initHTML(prog.PrintFunc)
|
||||
fn.startBody()
|
||||
fn.addSpilledParam(recv, nil)
|
||||
createParams(fn, start)
|
||||
|
||||
indices := sel.Index()
|
||||
|
||||
var v Value = fn.Locals[0] // spilled receiver
|
||||
if isPointer(sel.Recv()) {
|
||||
v = emitLoad(fn, v, nil)
|
||||
|
||||
// For simple indirection wrappers, perform an informative nil-check:
|
||||
// "value method (T).f called using nil *T pointer"
|
||||
if len(indices) == 1 && !isPointer(recvType(obj)) {
|
||||
var c Call
|
||||
c.Call.Value = &Builtin{
|
||||
name: "ir:wrapnilchk",
|
||||
sig: types.NewSignatureType(nil, nil, nil,
|
||||
types.NewTuple(anonVar(sel.Recv()), anonVar(tString), anonVar(tString)),
|
||||
types.NewTuple(anonVar(sel.Recv())), false),
|
||||
}
|
||||
c.Call.Args = []Value{
|
||||
v,
|
||||
emitConst(fn, stringConst(deref(sel.Recv()).String(), nil)),
|
||||
emitConst(fn, stringConst(sel.Obj().Name(), nil)),
|
||||
}
|
||||
c.setType(v.Type())
|
||||
v = fn.emit(&c, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Invariant: v is a pointer, either
|
||||
// value of *A receiver param, or
|
||||
// address of A spilled receiver.
|
||||
|
||||
// We use pointer arithmetic (FieldAddr possibly followed by
|
||||
// Load) in preference to value extraction (Field possibly
|
||||
// preceded by Load).
|
||||
|
||||
v = emitImplicitSelections(fn, v, indices[:len(indices)-1], nil)
|
||||
|
||||
// Invariant: v is a pointer, either
|
||||
// value of implicit *C field, or
|
||||
// address of implicit C field.
|
||||
|
||||
var c Call
|
||||
if r := recvType(obj); !types.IsInterface(r) { // concrete method
|
||||
if !isPointer(r) {
|
||||
v = emitLoad(fn, v, nil)
|
||||
}
|
||||
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, nil)
|
||||
}
|
||||
for _, arg := range fn.Params[1:] {
|
||||
c.Call.Args = append(c.Call.Args, arg)
|
||||
}
|
||||
emitTailCall(fn, &c, nil)
|
||||
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) {
|
||||
tparams := fn.Signature.Params()
|
||||
for i, n := start, tparams.Len(); i < n; i++ {
|
||||
fn.addParamVar(tparams.At(i), nil)
|
||||
}
|
||||
}
|
||||
|
||||
// -- 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()
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("%s", SyntheticBound)()
|
||||
}
|
||||
fn := &Function{
|
||||
name: obj.Name() + "$bound",
|
||||
object: obj,
|
||||
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
|
||||
Synthetic: SyntheticBound,
|
||||
Prog: prog,
|
||||
functionBody: new(functionBody),
|
||||
}
|
||||
fn.initHTML(prog.PrintFunc)
|
||||
|
||||
fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn}
|
||||
fn.FreeVars = []*FreeVar{fv}
|
||||
fn.startBody()
|
||||
createParams(fn, 0)
|
||||
var c Call
|
||||
|
||||
if !types.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, nil)
|
||||
fn.finishBody()
|
||||
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() }
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||
func makeThunk(prog *Program, sel *types.Selection) *Function {
|
||||
if sel.Kind() != types.MethodExpr {
|
||||
panic(sel)
|
||||
}
|
||||
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
|
||||
fn := makeWrapper(prog, sel)
|
||||
if fn.Signature.Recv() != nil {
|
||||
panic(fn) // unexpected receiver
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
func changeRecv(s *types.Signature, recv *types.Var) *types.Signature {
|
||||
return types.NewSignatureType(recv, nil, nil, s.Params(), s.Results(), s.Variadic())
|
||||
}
|
||||
|
||||
// makeInstance creates a wrapper function with signature sig that calls the generic function fn.
|
||||
// If targs is not nil, fn is a function and targs describes the concrete type arguments.
|
||||
// If targs is nil, fn is a method and the type arguments are derived from the receiver.
|
||||
func makeInstance(prog *Program, fn *Function, sig *types.Signature, targs *types.TypeList) *Function {
|
||||
if sig.Recv() != nil {
|
||||
assert(targs == nil)
|
||||
// Methods don't have their own type parameters, but the receiver does
|
||||
targs = types.Unalias(deref(sig.Recv().Type())).(*types.Named).TypeArgs()
|
||||
} else {
|
||||
assert(targs != nil)
|
||||
}
|
||||
|
||||
wrapper := fn.generics.At(targs)
|
||||
if wrapper != nil {
|
||||
return wrapper
|
||||
}
|
||||
|
||||
var name string
|
||||
if sig.Recv() != nil {
|
||||
name = fn.name
|
||||
} else {
|
||||
name = fmt.Sprintf("%s$generic#%d", fn.name, fn.generics.Len())
|
||||
}
|
||||
w := &Function{
|
||||
name: name,
|
||||
object: fn.object,
|
||||
Signature: sig,
|
||||
Synthetic: SyntheticGeneric,
|
||||
Prog: prog,
|
||||
functionBody: new(functionBody),
|
||||
}
|
||||
w.initHTML(prog.PrintFunc)
|
||||
w.startBody()
|
||||
if sig.Recv() != nil {
|
||||
w.addParamVar(sig.Recv(), nil)
|
||||
}
|
||||
createParams(w, 0)
|
||||
var c Call
|
||||
c.Call.Value = fn
|
||||
tresults := fn.Signature.Results()
|
||||
if tresults.Len() == 1 {
|
||||
c.typ = tresults.At(0).Type()
|
||||
} else {
|
||||
c.typ = tresults
|
||||
}
|
||||
|
||||
changeType := func(v Value, typ types.Type) Value {
|
||||
if types.Identical(v.Type(), typ) {
|
||||
return v
|
||||
}
|
||||
var c ChangeType
|
||||
c.X = v
|
||||
c.typ = typ
|
||||
return w.emit(&c, nil)
|
||||
}
|
||||
|
||||
for i, arg := range w.Params {
|
||||
if sig.Recv() != nil {
|
||||
if i == 0 {
|
||||
c.Call.Args = append(c.Call.Args, changeType(w.Params[0], fn.Signature.Recv().Type()))
|
||||
} else {
|
||||
c.Call.Args = append(c.Call.Args, changeType(arg, fn.Signature.Params().At(i-1).Type()))
|
||||
}
|
||||
} else {
|
||||
c.Call.Args = append(c.Call.Args, changeType(arg, fn.Signature.Params().At(i).Type()))
|
||||
}
|
||||
}
|
||||
for arg := range targs.Types() {
|
||||
c.Call.TypeArgs = append(c.Call.TypeArgs, arg)
|
||||
}
|
||||
results := w.emit(&c, nil)
|
||||
var ret Return
|
||||
switch tresults.Len() {
|
||||
case 0:
|
||||
case 1:
|
||||
ret.Results = []Value{changeType(results, sig.Results().At(0).Type())}
|
||||
default:
|
||||
for i := 0; i < tresults.Len(); i++ {
|
||||
v := emitExtract(w, results, i, nil)
|
||||
ret.Results = append(ret.Results, changeType(v, sig.Results().At(i).Type()))
|
||||
}
|
||||
}
|
||||
|
||||
w.Exit = w.newBasicBlock("exit")
|
||||
emitJump(w, w.Exit, nil)
|
||||
w.currentBlock = w.Exit
|
||||
w.emit(&ret, nil)
|
||||
w.currentBlock = nil
|
||||
|
||||
w.finishBody()
|
||||
|
||||
fn.generics.Set(targs, w)
|
||||
return w
|
||||
}
|
||||
5
vendor/honnef.co/go/tools/go/ir/write.go
vendored
Normal file
5
vendor/honnef.co/go/tools/go/ir/write.go
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package ir
|
||||
|
||||
func NewJump(parent *BasicBlock) *Jump {
|
||||
return &Jump{anInstruction{block: parent}}
|
||||
}
|
||||
98
vendor/honnef.co/go/tools/go/loader/hash.go
vendored
Normal file
98
vendor/honnef.co/go/tools/go/loader/hash.go
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/go/buildid"
|
||||
"honnef.co/go/tools/lintcmd/cache"
|
||||
)
|
||||
|
||||
// computeHash computes a package's hash. The hash is based on all Go
|
||||
// files that make up the package, as well as the hashes of imported
|
||||
// packages.
|
||||
func computeHash(c *cache.Cache, pkg *PackageSpec) (cache.ActionID, error) {
|
||||
key := c.NewHash("package " + pkg.PkgPath)
|
||||
fmt.Fprintf(key, "goos %s goarch %s\n", runtime.GOOS, runtime.GOARCH)
|
||||
fmt.Fprintf(key, "import %q\n", pkg.PkgPath)
|
||||
|
||||
// Compute the hashes of all files making up the package. As an
|
||||
// optimization, we use the build ID that Go already computed for us,
|
||||
// because it is virtually identical to hashing all CompiledGoFiles. It is
|
||||
// also sensitive to the Go version declared in go.mod.
|
||||
success := false
|
||||
if pkg.ExportFile != "" {
|
||||
id, err := getBuildid(pkg.ExportFile)
|
||||
if err == nil {
|
||||
if idx := strings.IndexRune(id, '/'); idx > -1 {
|
||||
fmt.Fprintf(key, "files %s\n", id[:idx])
|
||||
success = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
for _, f := range pkg.CompiledGoFiles {
|
||||
h, err := cache.FileHash(f)
|
||||
if err != nil {
|
||||
return cache.ActionID{}, err
|
||||
}
|
||||
fmt.Fprintf(key, "file %s %x\n", f, h)
|
||||
}
|
||||
if pkg.Module != nil && pkg.Module.GoMod != "" {
|
||||
// The go.mod file specifies the language version, which affects how
|
||||
// packages are analyzed.
|
||||
h, err := cache.FileHash(pkg.Module.GoMod)
|
||||
if err != nil {
|
||||
// TODO(dh): this doesn't work for tests because the go.mod file doesn't
|
||||
// exist on disk and is instead provided via an overlay. However, we're
|
||||
// unlikely to get here in the first place, as reading the build ID from
|
||||
// the export file is likely to succeed.
|
||||
return cache.ActionID{}, fmt.Errorf("couldn't hash go.mod: %w", err)
|
||||
} else {
|
||||
fmt.Fprintf(key, "file %s %x\n", pkg.Module.GoMod, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
imps := make([]*PackageSpec, 0, len(pkg.Imports))
|
||||
for _, v := range pkg.Imports {
|
||||
imps = append(imps, v)
|
||||
}
|
||||
sort.Slice(imps, func(i, j int) bool {
|
||||
return imps[i].PkgPath < imps[j].PkgPath
|
||||
})
|
||||
|
||||
for _, dep := range imps {
|
||||
if dep.ExportFile == "" {
|
||||
fmt.Fprintf(key, "import %s \n", dep.PkgPath)
|
||||
} else {
|
||||
id, err := getBuildid(dep.ExportFile)
|
||||
if err == nil {
|
||||
fmt.Fprintf(key, "import %s %s\n", dep.PkgPath, id)
|
||||
} else {
|
||||
fh, err := cache.FileHash(dep.ExportFile)
|
||||
if err != nil {
|
||||
return cache.ActionID{}, err
|
||||
}
|
||||
fmt.Fprintf(key, "import %s %x\n", dep.PkgPath, fh)
|
||||
}
|
||||
}
|
||||
}
|
||||
return key.Sum(), nil
|
||||
}
|
||||
|
||||
var buildidCache = map[string]string{}
|
||||
|
||||
func getBuildid(f string) (string, error) {
|
||||
if h, ok := buildidCache[f]; ok {
|
||||
return h, nil
|
||||
}
|
||||
h, err := buildid.ReadFile(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buildidCache[f] = h
|
||||
return h, nil
|
||||
}
|
||||
379
vendor/honnef.co/go/tools/go/loader/loader.go
vendored
Normal file
379
vendor/honnef.co/go/tools/go/loader/loader.go
vendored
Normal file
@@ -0,0 +1,379 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"honnef.co/go/tools/config"
|
||||
"honnef.co/go/tools/lintcmd/cache"
|
||||
|
||||
"golang.org/x/tools/go/gcexportdata"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
const MaxFileSize = 50 * 1024 * 1024 // 50 MB
|
||||
|
||||
var errMaxFileSize = errors.New("file exceeds max file size")
|
||||
|
||||
type PackageSpec struct {
|
||||
ID string
|
||||
Name string
|
||||
PkgPath string
|
||||
// Errors that occurred while building the import graph. These will
|
||||
// primarily be parse errors or failure to resolve imports, but
|
||||
// may also be other errors.
|
||||
Errors []packages.Error
|
||||
GoFiles []string
|
||||
CompiledGoFiles []string
|
||||
OtherFiles []string
|
||||
ExportFile string
|
||||
Imports map[string]*PackageSpec
|
||||
TypesSizes types.Sizes
|
||||
Hash cache.ActionID
|
||||
Module *packages.Module
|
||||
|
||||
Config config.Config
|
||||
}
|
||||
|
||||
func (spec *PackageSpec) String() string {
|
||||
return spec.ID
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
*PackageSpec
|
||||
|
||||
// Errors that occurred while loading the package. These will
|
||||
// primarily be parse or type errors, but may also be lower-level
|
||||
// failures such as file-system ones.
|
||||
Errors []packages.Error
|
||||
Types *types.Package
|
||||
Fset *token.FileSet
|
||||
Syntax []*ast.File
|
||||
TypesInfo *types.Info
|
||||
}
|
||||
|
||||
// Graph resolves patterns and returns packages with all the
|
||||
// information required to later load type information, and optionally
|
||||
// syntax trees.
|
||||
//
|
||||
// The provided config can set any setting with the exception of Mode.
|
||||
func Graph(c *cache.Cache, cfg *packages.Config, patterns ...string) ([]*PackageSpec, error) {
|
||||
var dcfg packages.Config
|
||||
if cfg != nil {
|
||||
dcfg = *cfg
|
||||
}
|
||||
dcfg.Mode = packages.NeedName |
|
||||
packages.NeedImports |
|
||||
packages.NeedDeps |
|
||||
packages.NeedExportFile |
|
||||
packages.NeedFiles |
|
||||
packages.NeedCompiledGoFiles |
|
||||
packages.NeedTypesSizes |
|
||||
packages.NeedModule
|
||||
pkgs, err := packages.Load(&dcfg, patterns...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := map[*packages.Package]*PackageSpec{}
|
||||
packages.Visit(pkgs, nil, func(pkg *packages.Package) {
|
||||
spec := &PackageSpec{
|
||||
ID: pkg.ID,
|
||||
Name: pkg.Name,
|
||||
PkgPath: pkg.PkgPath,
|
||||
Errors: pkg.Errors,
|
||||
GoFiles: pkg.GoFiles,
|
||||
CompiledGoFiles: pkg.CompiledGoFiles,
|
||||
OtherFiles: pkg.OtherFiles,
|
||||
ExportFile: pkg.ExportFile,
|
||||
Imports: map[string]*PackageSpec{},
|
||||
TypesSizes: pkg.TypesSizes,
|
||||
Module: pkg.Module,
|
||||
}
|
||||
for path, imp := range pkg.Imports {
|
||||
spec.Imports[path] = m[imp]
|
||||
}
|
||||
if cdir := config.Dir(pkg.GoFiles); cdir != "" {
|
||||
cfg, err := config.Load(cdir)
|
||||
if err != nil {
|
||||
spec.Errors = append(spec.Errors, convertError(err)...)
|
||||
}
|
||||
spec.Config = cfg
|
||||
} else {
|
||||
spec.Config = config.DefaultConfig
|
||||
}
|
||||
spec.Hash, err = computeHash(c, spec)
|
||||
if err != nil {
|
||||
spec.Errors = append(spec.Errors, convertError(err)...)
|
||||
}
|
||||
m[pkg] = spec
|
||||
})
|
||||
out := make([]*PackageSpec, 0, len(pkgs))
|
||||
for _, pkg := range pkgs {
|
||||
if len(pkg.CompiledGoFiles) == 0 && len(pkg.Errors) == 0 && pkg.PkgPath != "unsafe" {
|
||||
// If a package consists only of test files, then
|
||||
// go/packages incorrectly(?) returns an empty package for
|
||||
// the non-test variant. Get rid of those packages. See
|
||||
// #646.
|
||||
//
|
||||
// Do not, however, skip packages that have errors. Those,
|
||||
// too, may have no files, but we want to print the
|
||||
// errors.
|
||||
continue
|
||||
}
|
||||
out = append(out, m[pkg])
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type program struct {
|
||||
fset *token.FileSet
|
||||
packages map[string]*types.Package
|
||||
options *Options
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
Source time.Duration
|
||||
Export map[*PackageSpec]time.Duration
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
// The Go language version to use for the type checker. If unset, or if set
|
||||
// to "module", it will default to the Go version specified in the module;
|
||||
// if there is no module, it will default to the version of Go the
|
||||
// executable was built with.
|
||||
GoVersion string
|
||||
}
|
||||
|
||||
// Load loads the package described in spec. Imports will be loaded
|
||||
// from export data, while the package itself will be loaded from
|
||||
// source.
|
||||
//
|
||||
// An error will only be returned for system failures, such as failure
|
||||
// to read export data from disk. Syntax and type errors, among
|
||||
// others, will only populate the returned package's Errors field.
|
||||
func Load(spec *PackageSpec, opts *Options) (*Package, Stats, error) {
|
||||
if opts == nil {
|
||||
opts = &Options{}
|
||||
}
|
||||
if opts.GoVersion == "" {
|
||||
opts.GoVersion = "module"
|
||||
}
|
||||
prog := &program{
|
||||
fset: token.NewFileSet(),
|
||||
packages: map[string]*types.Package{},
|
||||
options: opts,
|
||||
}
|
||||
|
||||
stats := Stats{
|
||||
Export: map[*PackageSpec]time.Duration{},
|
||||
}
|
||||
for _, imp := range spec.Imports {
|
||||
if imp.PkgPath == "unsafe" {
|
||||
continue
|
||||
}
|
||||
t := time.Now()
|
||||
_, err := prog.loadFromExport(imp)
|
||||
stats.Export[imp] = time.Since(t)
|
||||
if err != nil {
|
||||
return nil, stats, err
|
||||
}
|
||||
}
|
||||
t := time.Now()
|
||||
pkg, err := prog.loadFromSource(spec)
|
||||
if err == errMaxFileSize {
|
||||
pkg, err = prog.loadFromExport(spec)
|
||||
}
|
||||
stats.Source = time.Since(t)
|
||||
return pkg, stats, err
|
||||
}
|
||||
|
||||
// loadFromExport loads a package from export data.
|
||||
func (prog *program) loadFromExport(spec *PackageSpec) (*Package, error) {
|
||||
// log.Printf("Loading package %s from export", spec)
|
||||
if spec.ExportFile == "" {
|
||||
return nil, fmt.Errorf("no export data for %q", spec.ID)
|
||||
}
|
||||
f, err := os.Open(spec.ExportFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
r, err := gcexportdata.NewReader(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tpkg, err := gcexportdata.Read(r, prog.fset, prog.packages, spec.PkgPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg := &Package{
|
||||
PackageSpec: spec,
|
||||
Types: tpkg,
|
||||
Fset: prog.fset,
|
||||
}
|
||||
// runtime.SetFinalizer(pkg, func(pkg *Package) {
|
||||
// log.Println("Unloading package", pkg.PkgPath)
|
||||
// })
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
// loadFromSource loads a package from source. All of its dependencies
|
||||
// must have been loaded already.
|
||||
func (prog *program) loadFromSource(spec *PackageSpec) (*Package, error) {
|
||||
if len(spec.Errors) > 0 {
|
||||
panic("LoadFromSource called on package with errors")
|
||||
}
|
||||
|
||||
pkg := &Package{
|
||||
PackageSpec: spec,
|
||||
Types: types.NewPackage(spec.PkgPath, spec.Name),
|
||||
Syntax: make([]*ast.File, len(spec.CompiledGoFiles)),
|
||||
Fset: prog.fset,
|
||||
TypesInfo: &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Implicits: make(map[ast.Node]types.Object),
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
Instances: make(map[*ast.Ident]types.Instance),
|
||||
FileVersions: make(map[*ast.File]string),
|
||||
},
|
||||
}
|
||||
// runtime.SetFinalizer(pkg, func(pkg *Package) {
|
||||
// log.Println("Unloading package", pkg.PkgPath)
|
||||
// })
|
||||
|
||||
// OPT(dh): many packages have few files, much fewer than there
|
||||
// are CPU cores. Additionally, parsing each individual file is
|
||||
// very fast. A naive parallel implementation of this loop won't
|
||||
// be faster, and tends to be slower due to extra scheduling,
|
||||
// bookkeeping and potentially false sharing of cache lines.
|
||||
for i, file := range spec.CompiledGoFiles {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fi.Size() >= MaxFileSize {
|
||||
return nil, errMaxFileSize
|
||||
}
|
||||
af, err := parser.ParseFile(prog.fset, file, f, parser.ParseComments|parser.SkipObjectResolution)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
pkg.Errors = append(pkg.Errors, convertError(err)...)
|
||||
return pkg, nil
|
||||
}
|
||||
pkg.Syntax[i] = af
|
||||
}
|
||||
importer := func(path string) (*types.Package, error) {
|
||||
if path == "unsafe" {
|
||||
return types.Unsafe, nil
|
||||
}
|
||||
if path == "C" {
|
||||
// go/packages doesn't tell us that cgo preprocessing
|
||||
// failed. When we subsequently try to parse the package,
|
||||
// we'll encounter the raw C import.
|
||||
return nil, errors.New("cgo preprocessing failed")
|
||||
}
|
||||
ispecpkg := spec.Imports[path]
|
||||
if ispecpkg == nil {
|
||||
return nil, fmt.Errorf("trying to import %q in the context of %q returned nil PackageSpec", path, spec)
|
||||
}
|
||||
ipkg := prog.packages[ispecpkg.PkgPath]
|
||||
if ipkg == nil {
|
||||
return nil, fmt.Errorf("trying to import %q (%q) in the context of %q returned nil PackageSpec", ispecpkg.PkgPath, path, spec)
|
||||
}
|
||||
return ipkg, nil
|
||||
}
|
||||
tc := &types.Config{
|
||||
Importer: importerFunc(importer),
|
||||
Error: func(err error) {
|
||||
pkg.Errors = append(pkg.Errors, convertError(err)...)
|
||||
},
|
||||
}
|
||||
if prog.options.GoVersion == "module" {
|
||||
if spec.Module != nil && spec.Module.GoVersion != "" {
|
||||
tc.GoVersion = "go" + spec.Module.GoVersion
|
||||
} else {
|
||||
tags := build.Default.ReleaseTags
|
||||
tc.GoVersion = tags[len(tags)-1]
|
||||
}
|
||||
} else {
|
||||
tc.GoVersion = prog.options.GoVersion
|
||||
}
|
||||
// Note that the type-checker can return a non-nil error even though the Go
|
||||
// compiler has already successfully built this package (which is an
|
||||
// invariant of getting to this point), for example because of the Go
|
||||
// version passed to the type checker.
|
||||
err := types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax)
|
||||
return pkg, err
|
||||
}
|
||||
|
||||
func convertError(err error) []packages.Error {
|
||||
var errs []packages.Error
|
||||
// taken from go/packages
|
||||
switch err := err.(type) {
|
||||
case packages.Error:
|
||||
// from driver
|
||||
errs = append(errs, err)
|
||||
|
||||
case *os.PathError:
|
||||
// from parser
|
||||
errs = append(errs, packages.Error{
|
||||
Pos: err.Path + ":1",
|
||||
Msg: err.Err.Error(),
|
||||
Kind: packages.ParseError,
|
||||
})
|
||||
|
||||
case scanner.ErrorList:
|
||||
// from parser
|
||||
for _, err := range err {
|
||||
errs = append(errs, packages.Error{
|
||||
Pos: err.Pos.String(),
|
||||
Msg: err.Msg,
|
||||
Kind: packages.ParseError,
|
||||
})
|
||||
}
|
||||
|
||||
case types.Error:
|
||||
// from type checker
|
||||
errs = append(errs, packages.Error{
|
||||
Pos: err.Fset.Position(err.Pos).String(),
|
||||
Msg: err.Msg,
|
||||
Kind: packages.TypeError,
|
||||
})
|
||||
|
||||
case config.ParseError:
|
||||
errs = append(errs, packages.Error{
|
||||
Pos: fmt.Sprintf("%s:%d:%d", err.Filename, err.Position.Line, err.Position.Col),
|
||||
Msg: fmt.Sprintf("%s (last key parsed: %q)", err.Message, err.LastKey),
|
||||
Kind: packages.ParseError,
|
||||
})
|
||||
default:
|
||||
errs = append(errs, packages.Error{
|
||||
Pos: "-",
|
||||
Msg: err.Error(),
|
||||
Kind: packages.UnknownError,
|
||||
})
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
type importerFunc func(path string) (*types.Package, error)
|
||||
|
||||
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
|
||||
27
vendor/honnef.co/go/tools/go/types/typeutil/ext.go
vendored
Normal file
27
vendor/honnef.co/go/tools/go/types/typeutil/ext.go
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package typeutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
type Iterator struct {
|
||||
elem types.Type
|
||||
}
|
||||
|
||||
func (t *Iterator) Underlying() types.Type { return t }
|
||||
func (t *Iterator) String() string { return fmt.Sprintf("iterator(%s)", t.elem) }
|
||||
func (t *Iterator) Elem() types.Type { return t.elem }
|
||||
|
||||
func NewIterator(elem types.Type) *Iterator {
|
||||
return &Iterator{elem: elem}
|
||||
}
|
||||
|
||||
type DeferStack struct{}
|
||||
|
||||
func (t *DeferStack) Underlying() types.Type { return t }
|
||||
func (t *DeferStack) String() string { return "deferStack" }
|
||||
|
||||
func NewDeferStack() *DeferStack {
|
||||
return &DeferStack{}
|
||||
}
|
||||
106
vendor/honnef.co/go/tools/go/types/typeutil/typeparams.go
vendored
Normal file
106
vendor/honnef.co/go/tools/go/types/typeutil/typeparams.go
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
package typeutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/types"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/exp/typeparams"
|
||||
)
|
||||
|
||||
type TypeSet struct {
|
||||
Terms []*types.Term
|
||||
empty bool
|
||||
}
|
||||
|
||||
func NewTypeSet(typ types.Type) TypeSet {
|
||||
terms, err := typeparams.NormalTerms(typ)
|
||||
if err != nil {
|
||||
if errors.Is(err, typeparams.ErrEmptyTypeSet) {
|
||||
return TypeSet{nil, true}
|
||||
} else {
|
||||
// We couldn't determine the type set. Assume it's all types.
|
||||
return TypeSet{nil, false}
|
||||
}
|
||||
}
|
||||
return TypeSet{terms, false}
|
||||
}
|
||||
|
||||
// CoreType returns the type set's core type, or nil if it has none.
|
||||
// The function only looks at type terms and may thus return core types for some empty type sets, such as
|
||||
// 'interface { map[int]string; foo() }'
|
||||
func (ts TypeSet) CoreType() types.Type {
|
||||
if len(ts.Terms) == 0 {
|
||||
// Either the type set is empty, or it isn't constrained. Either way it doesn't have a core type.
|
||||
return nil
|
||||
}
|
||||
typ := ts.Terms[0].Type().Underlying()
|
||||
for _, term := range ts.Terms[1:] {
|
||||
ut := term.Type().Underlying()
|
||||
if types.Identical(typ, ut) {
|
||||
continue
|
||||
}
|
||||
|
||||
ch1, ok := typ.(*types.Chan)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ch2, ok := ut.(*types.Chan)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if ch1.Dir() == types.SendRecv {
|
||||
// typ is currently a bidirectional channel. The term's type is either also bidirectional, or
|
||||
// unidirectional. Use the term's type.
|
||||
typ = ut
|
||||
} else if ch2.Dir() == types.SendRecv {
|
||||
// typ is currently a unidirectional channel and the term's type is bidirectional, which means it has no
|
||||
// effect.
|
||||
continue
|
||||
} else if ch1.Dir() != ch2.Dir() {
|
||||
// typ is not bidirectional and typ and term disagree about the direction
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// CoreType is a wrapper for NewTypeSet(typ).CoreType()
|
||||
func CoreType(typ types.Type) types.Type {
|
||||
return NewTypeSet(typ).CoreType()
|
||||
}
|
||||
|
||||
// All calls fn for each term in the type set and reports whether all invocations returned true.
|
||||
// If the type set is empty or unconstrained, All immediately returns false.
|
||||
func (ts TypeSet) All(fn func(*types.Term) bool) bool {
|
||||
if len(ts.Terms) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, term := range ts.Terms {
|
||||
if !fn(term) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Any calls fn for each term in the type set and reports whether any invocation returned true.
|
||||
// It stops after the first call that returned true.
|
||||
func (ts TypeSet) Any(fn func(*types.Term) bool) bool {
|
||||
return slices.ContainsFunc(ts.Terms, fn)
|
||||
}
|
||||
|
||||
// All is a wrapper for NewTypeSet(typ).All(fn).
|
||||
func All(typ types.Type, fn func(*types.Term) bool) bool {
|
||||
return NewTypeSet(typ).All(fn)
|
||||
}
|
||||
|
||||
// Any is a wrapper for NewTypeSet(typ).Any(fn).
|
||||
func Any(typ types.Type, fn func(*types.Term) bool) bool {
|
||||
return NewTypeSet(typ).Any(fn)
|
||||
}
|
||||
|
||||
func IsSlice(term *types.Term) bool {
|
||||
_, ok := term.Type().Underlying().(*types.Slice)
|
||||
return ok
|
||||
}
|
||||
52
vendor/honnef.co/go/tools/go/types/typeutil/upstream.go
vendored
Normal file
52
vendor/honnef.co/go/tools/go/types/typeutil/upstream.go
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package typeutil
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
_ "unsafe"
|
||||
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
type MethodSetCache = typeutil.MethodSetCache
|
||||
type Hasher = typeutil.Hasher
|
||||
|
||||
func Callee(info *types.Info, call *ast.CallExpr) types.Object {
|
||||
return typeutil.Callee(info, call)
|
||||
}
|
||||
|
||||
func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection {
|
||||
return typeutil.IntuitiveMethodSet(T, msets)
|
||||
}
|
||||
|
||||
func MakeHasher() Hasher {
|
||||
return typeutil.MakeHasher()
|
||||
}
|
||||
|
||||
type Map[V any] struct {
|
||||
m typeutil.Map
|
||||
}
|
||||
|
||||
func (m *Map[V]) Delete(key types.Type) bool { return m.m.Delete(key) }
|
||||
func (m *Map[V]) At(key types.Type) (V, bool) {
|
||||
v := m.m.At(key)
|
||||
if v == nil {
|
||||
var zero V
|
||||
return zero, false
|
||||
} else {
|
||||
return v.(V), true
|
||||
}
|
||||
}
|
||||
func (m *Map[V]) Set(key types.Type, value V) { m.m.Set(key, value) }
|
||||
func (m *Map[V]) Len() int { return m.m.Len() }
|
||||
func (m *Map[V]) Iterate(f func(key types.Type, value V)) {
|
||||
ff := func(key types.Type, value any) {
|
||||
f(key, value.(V))
|
||||
}
|
||||
m.m.Iterate(ff)
|
||||
|
||||
}
|
||||
func (m *Map[V]) Keys() []types.Type { return m.m.Keys() }
|
||||
func (m *Map[V]) String() string { return m.m.String() }
|
||||
func (m *Map[V]) KeysString() string { return m.m.KeysString() }
|
||||
func (m *Map[V]) SetHasher(h typeutil.Hasher) { m.m.SetHasher(h) }
|
||||
211
vendor/honnef.co/go/tools/go/types/typeutil/util.go
vendored
Normal file
211
vendor/honnef.co/go/tools/go/types/typeutil/util.go
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
package typeutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/types"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/exp/typeparams"
|
||||
)
|
||||
|
||||
var bufferPool = &sync.Pool{
|
||||
New: func() any {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.Grow(64)
|
||||
return buf
|
||||
},
|
||||
}
|
||||
|
||||
func FuncName(f *types.Func) string {
|
||||
// We don't care about aliases in this function because we use FuncName to check calls
|
||||
// to known methods, and method receivers are determined by the method declaration,
|
||||
// not the call. Thus, even if a user does 'type Alias = *sync.Mutex' and calls
|
||||
// Alias.Lock, we'll still see it as (*sync.Mutex).Lock.
|
||||
|
||||
buf := bufferPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
if f.Type() != nil {
|
||||
sig := f.Type().(*types.Signature)
|
||||
if recv := sig.Recv(); recv != nil {
|
||||
buf.WriteByte('(')
|
||||
if _, ok := recv.Type().(*types.Interface); ok {
|
||||
// gcimporter creates abstract methods of
|
||||
// named interfaces using the interface type
|
||||
// (not the named type) as the receiver.
|
||||
// Don't print it in full.
|
||||
buf.WriteString("interface")
|
||||
} else {
|
||||
types.WriteType(buf, recv.Type(), nil)
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
buf.WriteByte('.')
|
||||
} else if f.Pkg() != nil {
|
||||
writePackage(buf, f.Pkg())
|
||||
}
|
||||
}
|
||||
buf.WriteString(f.Name())
|
||||
s := buf.String()
|
||||
bufferPool.Put(buf)
|
||||
return s
|
||||
}
|
||||
|
||||
func writePackage(buf *bytes.Buffer, pkg *types.Package) {
|
||||
if pkg == nil {
|
||||
return
|
||||
}
|
||||
s := pkg.Path()
|
||||
if s != "" {
|
||||
buf.WriteString(s)
|
||||
buf.WriteByte('.')
|
||||
}
|
||||
}
|
||||
|
||||
// Dereference returns a pointer's element type; otherwise it returns
|
||||
// T.
|
||||
func Dereference(T types.Type) types.Type {
|
||||
if p, ok := T.Underlying().(*types.Pointer); ok {
|
||||
return p.Elem()
|
||||
}
|
||||
return T
|
||||
}
|
||||
|
||||
// DereferenceR returns a pointer's element type; otherwise it returns
|
||||
// T. If the element type is itself a pointer, DereferenceR will be
|
||||
// applied recursively.
|
||||
func DereferenceR(T types.Type) types.Type {
|
||||
if p, ok := T.Underlying().(*types.Pointer); ok {
|
||||
return DereferenceR(p.Elem())
|
||||
}
|
||||
return T
|
||||
}
|
||||
|
||||
func IsObject(obj types.Object, name string) bool {
|
||||
var path string
|
||||
if pkg := obj.Pkg(); pkg != nil {
|
||||
path = pkg.Path() + "."
|
||||
}
|
||||
return path+obj.Name() == name
|
||||
}
|
||||
|
||||
// IsTypeName reports whether obj represents the qualified name. If obj is a type alias,
|
||||
// IsTypeName checks both the alias and the aliased type, if the aliased type has a type
|
||||
// name.
|
||||
func IsTypeName(obj *types.TypeName, name string) bool {
|
||||
var qf string
|
||||
if idx := strings.LastIndex(name, "."); idx != -1 {
|
||||
qf = name[:idx]
|
||||
name = name[idx+1:]
|
||||
}
|
||||
if obj.Name() == name &&
|
||||
((qf == "" && obj.Pkg() == nil) || (obj.Pkg() != nil && obj.Pkg().Path() == qf)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if !obj.IsAlias() {
|
||||
return false
|
||||
}
|
||||
|
||||
// FIXME(dh): we should peel away one layer of alias at a time; this is blocked on
|
||||
// github.com/golang/go/issues/66559
|
||||
if typ, ok := types.Unalias(obj.Type()).(interface{ Obj() *types.TypeName }); ok {
|
||||
return IsTypeName(typ.Obj(), name)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IsPointerToTypeWithName(typ types.Type, name string) bool {
|
||||
ptr, ok := types.Unalias(typ).(*types.Pointer)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return IsTypeWithName(ptr.Elem(), name)
|
||||
}
|
||||
|
||||
// IsTypeWithName reports whether typ represents a type with the qualified name, If typ is
|
||||
// a type alias, IsTypeWithName checks both the alias and the aliased type. The following
|
||||
// types can have names: Basic, Named, Alias.
|
||||
func IsTypeWithName(typ types.Type, name string) bool {
|
||||
switch typ := typ.(type) {
|
||||
case *types.Basic:
|
||||
return typ.Name() == name
|
||||
case *types.Named:
|
||||
return IsTypeName(typ.Obj(), name)
|
||||
case *types.Alias:
|
||||
// FIXME(dh): we should peel away one layer of alias at a time; this is blocked on
|
||||
// github.com/golang/go/issues/66559
|
||||
|
||||
// IsTypeName already handles aliases to other aliases or named types; our
|
||||
// fallback is required for aliases to basic types.
|
||||
return IsTypeName(typ.Obj(), name) || IsTypeWithName(types.Unalias(typ), name)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsPointerLike returns true if type T is like a pointer. This returns true for all nillable types,
|
||||
// unsafe.Pointer, and type sets where at least one term is pointer-like.
|
||||
func IsPointerLike(T types.Type) bool {
|
||||
switch T := T.Underlying().(type) {
|
||||
case *types.Interface:
|
||||
if T.IsMethodSet() {
|
||||
return true
|
||||
} else {
|
||||
terms, err := typeparams.NormalTerms(T)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, term := range terms {
|
||||
if IsPointerLike(term.Type()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
case *types.Chan, *types.Map, *types.Signature, *types.Pointer, *types.Slice:
|
||||
return true
|
||||
case *types.Basic:
|
||||
return T.Kind() == types.UnsafePointer
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Var *types.Var
|
||||
Tag string
|
||||
Path []int
|
||||
}
|
||||
|
||||
// FlattenFields recursively flattens T and embedded structs,
|
||||
// returning a list of fields. If multiple fields with the same name
|
||||
// exist, all will be returned.
|
||||
func FlattenFields(T *types.Struct) []Field {
|
||||
return flattenFields(T, nil, nil)
|
||||
}
|
||||
|
||||
func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field {
|
||||
if seen == nil {
|
||||
seen = map[types.Type]bool{}
|
||||
}
|
||||
if seen[T] {
|
||||
return nil
|
||||
}
|
||||
seen[T] = true
|
||||
var out []Field
|
||||
for i := 0; i < T.NumFields(); i++ {
|
||||
field := T.Field(i)
|
||||
tag := T.Tag(i)
|
||||
np := append(path[:len(path):len(path)], i)
|
||||
if field.Anonymous() {
|
||||
if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok {
|
||||
out = append(out, flattenFields(s, np, seen)...)
|
||||
} else {
|
||||
out = append(out, Field{field, tag, np})
|
||||
}
|
||||
} else {
|
||||
out = append(out, Field{field, tag, np})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
33
vendor/honnef.co/go/tools/internal/analysisinternal/typeindex/typeindex.go
vendored
Normal file
33
vendor/honnef.co/go/tools/internal/analysisinternal/typeindex/typeindex.go
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package typeindex defines an analyzer that provides a
|
||||
// [golang.org/x/tools/internal/typesinternal/typeindex.Index].
|
||||
//
|
||||
// Like [golang.org/x/tools/go/analysis/passes/inspect], it is
|
||||
// intended to be used as a helper by other analyzers; it reports no
|
||||
// diagnostics of its own.
|
||||
package typeindex
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"honnef.co/go/tools/internal/typesinternal/typeindex"
|
||||
)
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "typeindex",
|
||||
Doc: "indexes of type information for later passes",
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/internal/analysisinternal/typeindex",
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
return typeindex.New(inspect, pass.Pkg, pass.TypesInfo), nil
|
||||
},
|
||||
RunDespiteErrors: true,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
ResultType: reflect.TypeFor[*typeindex.Index](),
|
||||
}
|
||||
101
vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go
vendored
Normal file
101
vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package buildir defines an Analyzer that constructs the IR
|
||||
// of an error-free package and returns the set of all
|
||||
// functions within it. It does not report any diagnostics itself but
|
||||
// may be used as an input to other analyzers.
|
||||
//
|
||||
// THIS INTERFACE IS EXPERIMENTAL AND MAY BE SUBJECT TO INCOMPATIBLE CHANGE.
|
||||
package buildir
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"reflect"
|
||||
|
||||
"honnef.co/go/tools/go/ir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/ctrlflow"
|
||||
)
|
||||
|
||||
var Debug = struct {
|
||||
Mode ir.BuilderMode
|
||||
}{}
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "buildir",
|
||||
Doc: "build IR for later passes",
|
||||
Run: run,
|
||||
ResultType: reflect.TypeFor[*IR](),
|
||||
Requires: []*analysis.Analyzer{ctrlflow.Analyzer},
|
||||
}
|
||||
|
||||
// IR provides intermediate representation for all the
|
||||
// source functions in the current package.
|
||||
type IR struct {
|
||||
Pkg *ir.Package
|
||||
SrcFuncs []*ir.Function
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs)
|
||||
|
||||
// Plundered from ssautil.BuildPackage.
|
||||
|
||||
// We must create a new Program for each Package because the
|
||||
// analysis API provides no place to hang a Program shared by
|
||||
// all Packages. Consequently, IR Packages and Functions do not
|
||||
// have a canonical representation across an analysis session of
|
||||
// multiple packages. This is unlikely to be a problem in
|
||||
// practice because the analysis API essentially forces all
|
||||
// packages to be analysed independently, so any given call to
|
||||
// Analysis.Run on a package will see only IR objects belonging
|
||||
// to a single Program.
|
||||
|
||||
mode := ir.GlobalDebug
|
||||
if Debug.Mode != 0 {
|
||||
mode = Debug.Mode
|
||||
}
|
||||
|
||||
prog := ir.NewProgram(pass.Fset, mode)
|
||||
|
||||
prog.SetNoReturn(cfgs.NoReturn)
|
||||
|
||||
// Create IR packages for 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(pass.Pkg.Imports())
|
||||
|
||||
// Create and build the primary package.
|
||||
irpkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false)
|
||||
irpkg.Build()
|
||||
|
||||
// Compute list of source functions, including literals,
|
||||
// in source order.
|
||||
var addAnons func(f *ir.Function)
|
||||
funcs := make([]*ir.Function, len(irpkg.Functions))
|
||||
copy(funcs, irpkg.Functions)
|
||||
addAnons = func(f *ir.Function) {
|
||||
for _, anon := range f.AnonFuncs {
|
||||
funcs = append(funcs, anon)
|
||||
addAnons(anon)
|
||||
}
|
||||
}
|
||||
for _, fn := range irpkg.Functions {
|
||||
addAnons(fn)
|
||||
}
|
||||
|
||||
return &IR{Pkg: irpkg, SrcFuncs: funcs}, nil
|
||||
}
|
||||
2
vendor/honnef.co/go/tools/internal/renameio/UPSTREAM
vendored
Normal file
2
vendor/honnef.co/go/tools/internal/renameio/UPSTREAM
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
This package is a copy of cmd/go/internal/renameio.
|
||||
The upstream package no longer exists, as the Go project replaced all of its uses with the lockedfile package.
|
||||
93
vendor/honnef.co/go/tools/internal/renameio/renameio.go
vendored
Normal file
93
vendor/honnef.co/go/tools/internal/renameio/renameio.go
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package renameio writes files atomically by renaming temporary files.
|
||||
package renameio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"honnef.co/go/tools/internal/robustio"
|
||||
)
|
||||
|
||||
const patternSuffix = ".tmp"
|
||||
|
||||
// Pattern returns a glob pattern that matches the unrenamed temporary files
|
||||
// created when writing to filename.
|
||||
func Pattern(filename string) string {
|
||||
return filepath.Join(filepath.Dir(filename), filepath.Base(filename)+patternSuffix)
|
||||
}
|
||||
|
||||
// WriteFile is like ioutil.WriteFile, but first writes data to an arbitrary
|
||||
// file in the same directory as filename, then renames it atomically to the
|
||||
// final name.
|
||||
//
|
||||
// That ensures that the final location, if it exists, is always a complete file.
|
||||
func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
|
||||
return WriteToFile(filename, bytes.NewReader(data), perm)
|
||||
}
|
||||
|
||||
// WriteToFile is a variant of WriteFile that accepts the data as an io.Reader
|
||||
// instead of a slice.
|
||||
func WriteToFile(filename string, data io.Reader, perm os.FileMode) (err error) {
|
||||
f, err := tempFile(filepath.Dir(filename), filepath.Base(filename), perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
// Only call os.Remove on f.Name() if we failed to rename it: otherwise,
|
||||
// some other process may have created a new file with the same name after
|
||||
// that.
|
||||
if err != nil {
|
||||
f.Close()
|
||||
os.Remove(f.Name())
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := io.Copy(f, data); err != nil {
|
||||
return err
|
||||
}
|
||||
// Sync the file before renaming it: otherwise, after a crash the reader may
|
||||
// observe a 0-length file instead of the actual contents.
|
||||
// See https://golang.org/issue/22397#issuecomment-380831736.
|
||||
if err := f.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return robustio.Rename(f.Name(), filename)
|
||||
}
|
||||
|
||||
// ReadFile is like ioutil.ReadFile, but on Windows retries spurious errors that
|
||||
// may occur if the file is concurrently replaced.
|
||||
//
|
||||
// Errors are classified heuristically and retries are bounded, so even this
|
||||
// function may occasionally return a spurious error on Windows.
|
||||
// If so, the error will likely wrap one of:
|
||||
// - syscall.ERROR_ACCESS_DENIED
|
||||
// - syscall.ERROR_FILE_NOT_FOUND
|
||||
// - internal/syscall/windows.ERROR_SHARING_VIOLATION
|
||||
func ReadFile(filename string) ([]byte, error) {
|
||||
return robustio.ReadFile(filename)
|
||||
}
|
||||
|
||||
// tempFile creates a new temporary file with given permission bits.
|
||||
func tempFile(dir, prefix string, perm os.FileMode) (f *os.File, err error) {
|
||||
for range 10000 {
|
||||
name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix)
|
||||
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
|
||||
if os.IsExist(err) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
6
vendor/honnef.co/go/tools/internal/robustio/UPSTREAM
vendored
Normal file
6
vendor/honnef.co/go/tools/internal/robustio/UPSTREAM
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
This package is a copy of cmd/go/internal/robustio.
|
||||
It is mostly in sync with upstream according to the last commit we've looked at,
|
||||
with the exception of still using I/O functions that work with older Go versions.
|
||||
|
||||
The last upstream commit we've looked at was:
|
||||
dc04f3ba1f25313bc9c97e728620206c235db9ee
|
||||
53
vendor/honnef.co/go/tools/internal/robustio/robustio.go
vendored
Normal file
53
vendor/honnef.co/go/tools/internal/robustio/robustio.go
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package robustio wraps I/O functions that are prone to failure on Windows,
|
||||
// transparently retrying errors up to an arbitrary timeout.
|
||||
//
|
||||
// Errors are classified heuristically and retries are bounded, so the functions
|
||||
// in this package do not completely eliminate spurious errors. However, they do
|
||||
// significantly reduce the rate of failure in practice.
|
||||
//
|
||||
// If so, the error will likely wrap one of:
|
||||
// The functions in this package do not completely eliminate spurious errors,
|
||||
// but substantially reduce their rate of occurrence in practice.
|
||||
package robustio
|
||||
|
||||
// Rename is like os.Rename, but on Windows retries errors that may occur if the
|
||||
// file is concurrently read or overwritten.
|
||||
//
|
||||
// (See golang.org/issue/31247 and golang.org/issue/32188.)
|
||||
func Rename(oldpath, newpath string) error {
|
||||
return rename(oldpath, newpath)
|
||||
}
|
||||
|
||||
// ReadFile is like os.ReadFile, but on Windows retries errors that may
|
||||
// occur if the file is concurrently replaced.
|
||||
//
|
||||
// (See golang.org/issue/31247 and golang.org/issue/32188.)
|
||||
func ReadFile(filename string) ([]byte, error) {
|
||||
return readFile(filename)
|
||||
}
|
||||
|
||||
// RemoveAll is like os.RemoveAll, but on Windows retries errors that may occur
|
||||
// if an executable file in the directory has recently been executed.
|
||||
//
|
||||
// (See golang.org/issue/19491.)
|
||||
func RemoveAll(path string) error {
|
||||
return removeAll(path)
|
||||
}
|
||||
|
||||
// IsEphemeralError reports whether err is one of the errors that the functions
|
||||
// in this package attempt to mitigate.
|
||||
//
|
||||
// Errors considered ephemeral include:
|
||||
// - syscall.ERROR_ACCESS_DENIED
|
||||
// - syscall.ERROR_FILE_NOT_FOUND
|
||||
// - internal/syscall/windows.ERROR_SHARING_VIOLATION
|
||||
//
|
||||
// This set may be expanded in the future; programs must not rely on the
|
||||
// non-ephemerality of any given error.
|
||||
func IsEphemeralError(err error) bool {
|
||||
return isEphemeralError(err)
|
||||
}
|
||||
21
vendor/honnef.co/go/tools/internal/robustio/robustio_darwin.go
vendored
Normal file
21
vendor/honnef.co/go/tools/internal/robustio/robustio_darwin.go
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package robustio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const errFileNotFound = syscall.ENOENT
|
||||
|
||||
// isEphemeralError returns true if err may be resolved by waiting.
|
||||
func isEphemeralError(err error) bool {
|
||||
var errno syscall.Errno
|
||||
if errors.As(err, &errno) {
|
||||
return errno == errFileNotFound
|
||||
}
|
||||
return false
|
||||
}
|
||||
91
vendor/honnef.co/go/tools/internal/robustio/robustio_flaky.go
vendored
Normal file
91
vendor/honnef.co/go/tools/internal/robustio/robustio_flaky.go
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows || darwin
|
||||
|
||||
package robustio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const arbitraryTimeout = 2000 * time.Millisecond
|
||||
|
||||
// retry retries ephemeral errors from f up to an arbitrary timeout
|
||||
// to work around filesystem flakiness on Windows and Darwin.
|
||||
func retry(f func() (err error, mayRetry bool)) error {
|
||||
var (
|
||||
bestErr error
|
||||
lowestErrno syscall.Errno
|
||||
start time.Time
|
||||
nextSleep time.Duration = 1 * time.Millisecond
|
||||
)
|
||||
for {
|
||||
err, mayRetry := f()
|
||||
if err == nil || !mayRetry {
|
||||
return err
|
||||
}
|
||||
|
||||
var errno syscall.Errno
|
||||
if errors.As(err, &errno) && (lowestErrno == 0 || errno < lowestErrno) {
|
||||
bestErr = err
|
||||
lowestErrno = errno
|
||||
} else if bestErr == nil {
|
||||
bestErr = err
|
||||
}
|
||||
|
||||
if start.IsZero() {
|
||||
start = time.Now()
|
||||
} else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
|
||||
break
|
||||
}
|
||||
time.Sleep(nextSleep)
|
||||
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
|
||||
}
|
||||
|
||||
return bestErr
|
||||
}
|
||||
|
||||
// rename is like os.Rename, but retries ephemeral errors.
|
||||
//
|
||||
// On Windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with
|
||||
// MOVEFILE_REPLACE_EXISTING.
|
||||
//
|
||||
// Windows also provides a different system call, ReplaceFile,
|
||||
// that provides similar semantics, but perhaps preserves more metadata. (The
|
||||
// documentation on the differences between the two is very sparse.)
|
||||
//
|
||||
// Empirical error rates with MoveFileEx are lower under modest concurrency, so
|
||||
// for now we're sticking with what the os package already provides.
|
||||
func rename(oldpath, newpath string) (err error) {
|
||||
return retry(func() (err error, mayRetry bool) {
|
||||
err = os.Rename(oldpath, newpath)
|
||||
return err, isEphemeralError(err)
|
||||
})
|
||||
}
|
||||
|
||||
// readFile is like os.ReadFile, but retries ephemeral errors.
|
||||
func readFile(filename string) ([]byte, error) {
|
||||
var b []byte
|
||||
err := retry(func() (err error, mayRetry bool) {
|
||||
b, err = os.ReadFile(filename)
|
||||
|
||||
// Unlike in rename, we do not retry errFileNotFound here: it can occur
|
||||
// as a spurious error, but the file may also genuinely not exist, so the
|
||||
// increase in robustness is probably not worth the extra latency.
|
||||
return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound)
|
||||
})
|
||||
return b, err
|
||||
}
|
||||
|
||||
func removeAll(path string) error {
|
||||
return retry(func() (err error, mayRetry bool) {
|
||||
err = os.RemoveAll(path)
|
||||
return err, isEphemeralError(err)
|
||||
})
|
||||
}
|
||||
27
vendor/honnef.co/go/tools/internal/robustio/robustio_other.go
vendored
Normal file
27
vendor/honnef.co/go/tools/internal/robustio/robustio_other.go
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows && !darwin
|
||||
|
||||
package robustio
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func rename(oldpath, newpath string) error {
|
||||
return os.Rename(oldpath, newpath)
|
||||
}
|
||||
|
||||
func readFile(filename string) ([]byte, error) {
|
||||
return os.ReadFile(filename)
|
||||
}
|
||||
|
||||
func removeAll(path string) error {
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
func isEphemeralError(err error) bool {
|
||||
return false
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user