mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-12 09:23:09 -06:00
Merge upstream again... since the last time didn't take
Signed-off-by: Ross Schulman <ross@rbs.io>
This commit is contained in:
commit
9aa9d53a39
14
.travis.yml
14
.travis.yml
|
|
@ -1,6 +1,12 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.8
|
||||
- 1.9
|
||||
|
||||
env:
|
||||
- TEST_SUITE="lint"
|
||||
- TEST_SUITE="unit-test"
|
||||
- TEST_SUITE="integ-test"
|
||||
|
||||
sudo: false
|
||||
|
||||
|
|
@ -8,9 +14,6 @@ sudo: false
|
|||
dist: trusty
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- openssl
|
||||
postgresql: "9.5"
|
||||
|
||||
services:
|
||||
|
|
@ -19,12 +22,7 @@ services:
|
|||
install:
|
||||
- go get github.com/constabulary/gb/...
|
||||
|
||||
# Generate a self-signed X.509 certificate for TLS.
|
||||
before_script:
|
||||
- openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes -subj /CN=localhost
|
||||
|
||||
script:
|
||||
- ./scripts/install-local-kafka.sh
|
||||
- ./scripts/travis-test.sh
|
||||
|
||||
notifications:
|
||||
|
|
|
|||
|
|
@ -54,6 +54,24 @@ media:
|
|||
height: 600
|
||||
method: scale
|
||||
|
||||
# The config for the TURN server
|
||||
turn:
|
||||
# Whether or not guests can request TURN credentials
|
||||
turn_allow_guests: true
|
||||
# How long the authorization should last
|
||||
turn_user_lifetime: "1h"
|
||||
# The list of TURN URIs to pass to clients
|
||||
turn_uris: []
|
||||
|
||||
# Authorization via Shared Secret
|
||||
# The shared secret from coturn
|
||||
turn_shared_secret: "<SECRET STRING GOES HERE>"
|
||||
|
||||
# Authorization via Static Username & Password
|
||||
# Hardcoded Username and Password
|
||||
turn_username: ""
|
||||
turn_password: ""
|
||||
|
||||
# The config for communicating with kafka
|
||||
kafka:
|
||||
# Where the kafka servers are running.
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
set -eu
|
||||
|
||||
./scripts/build-test-lint.sh
|
||||
./scripts/find-lint.sh fast
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
"Deadline": "5m",
|
||||
"Enable": [
|
||||
"vetshadow",
|
||||
"gotype",
|
||||
"deadcode",
|
||||
"gocyclo",
|
||||
"ineffassign",
|
||||
|
|
@ -12,6 +11,7 @@
|
|||
"misspell",
|
||||
"errcheck",
|
||||
"vet",
|
||||
"gofmt",
|
||||
"goconst"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@
|
|||
"Deadline": "5m",
|
||||
"Enable": [
|
||||
"vetshadow",
|
||||
"gotype",
|
||||
"deadcode",
|
||||
"gocyclo",
|
||||
"golint",
|
||||
"varcheck",
|
||||
"structcheck",
|
||||
"aligncheck",
|
||||
"maligned",
|
||||
"ineffassign",
|
||||
"gas",
|
||||
"misspell",
|
||||
|
|
@ -18,6 +17,7 @@
|
|||
"errcheck",
|
||||
"vet",
|
||||
"megacheck",
|
||||
"gofmt",
|
||||
"goconst"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
set -eu
|
||||
|
||||
export GOPATH="$(pwd):$(pwd)/vendor"
|
||||
export PATH="$PATH:$(pwd)/vendor/bin:$(pwd)/bin"
|
||||
export PATH="$PATH:$(pwd)/bin"
|
||||
|
||||
echo "Checking that it builds"
|
||||
gb build
|
||||
|
|
@ -19,8 +19,5 @@ go build github.com/matrix-org/dendrite/cmd/...
|
|||
|
||||
./scripts/find-lint.sh
|
||||
|
||||
echo "Double checking spelling..."
|
||||
misspell -error src *.md
|
||||
|
||||
echo "Testing..."
|
||||
gb test
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
set -eu
|
||||
|
||||
export GOPATH="$(pwd):$(pwd)/vendor"
|
||||
export PATH="$PATH:$(pwd)/vendor/bin:$(pwd)/bin"
|
||||
export PATH="$PATH:$(pwd)/bin"
|
||||
|
||||
args=""
|
||||
if [ ${1:-""} = "fast" ]
|
||||
|
|
@ -31,7 +31,7 @@ then args="$args --enable-gc"
|
|||
fi
|
||||
|
||||
echo "Installing lint search engine..."
|
||||
go install github.com/alecthomas/gometalinter/
|
||||
gb build github.com/alecthomas/gometalinter/
|
||||
gometalinter --config=linter.json ./... --install
|
||||
|
||||
echo "Looking for lint..."
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
#! /bin/bash
|
||||
|
||||
# The entry point for travis tests
|
||||
#
|
||||
# TEST_SUITE env var can be set to "lint", "unit-test" or "integ-test", in
|
||||
# which case only the linting, unit tests or integration tests will be run
|
||||
# respectively. If not specified or null all tests are run.
|
||||
|
||||
set -eu
|
||||
|
||||
|
|
@ -8,20 +12,44 @@ set -eu
|
|||
export GOGC=400
|
||||
export DENDRITE_LINT_DISABLE_GC=1
|
||||
|
||||
# Check that the servers build (this is done explicitly because `gb build` can silently fail (exit 0) and then we'd test a stale binary)
|
||||
gb build github.com/matrix-org/dendrite/cmd/dendrite-room-server
|
||||
gb build github.com/matrix-org/dendrite/cmd/roomserver-integration-tests
|
||||
gb build github.com/matrix-org/dendrite/cmd/dendrite-sync-api-server
|
||||
gb build github.com/matrix-org/dendrite/cmd/syncserver-integration-tests
|
||||
gb build github.com/matrix-org/dendrite/cmd/create-account
|
||||
gb build github.com/matrix-org/dendrite/cmd/dendrite-media-api-server
|
||||
gb build github.com/matrix-org/dendrite/cmd/mediaapi-integration-tests
|
||||
gb build github.com/matrix-org/dendrite/cmd/client-api-proxy
|
||||
export GOPATH="$(pwd):$(pwd)/vendor"
|
||||
export PATH="$PATH:$(pwd)/bin"
|
||||
|
||||
# Run unit tests and linters
|
||||
./scripts/build-test-lint.sh
|
||||
if [ "${TEST_SUITE:-lint}" == "lint" ]; then
|
||||
./scripts/find-lint.sh
|
||||
fi
|
||||
|
||||
# Run the integration tests
|
||||
bin/roomserver-integration-tests
|
||||
bin/syncserver-integration-tests
|
||||
bin/mediaapi-integration-tests
|
||||
if [ "${TEST_SUITE:-unit-test}" == "unit-test" ]; then
|
||||
gb test
|
||||
fi
|
||||
|
||||
if [ "${TEST_SUITE:-integ-test}" == "integ-test" ]; then
|
||||
gb build
|
||||
|
||||
# Check that all the packages can build.
|
||||
# When `go build` is given multiple packages it won't output anything, and just
|
||||
# checks that everything builds. This seems to do a better job of handling
|
||||
# missing imports than `gb build` does.
|
||||
go build github.com/matrix-org/dendrite/cmd/...
|
||||
|
||||
# Check that the servers build (this is done explicitly because `gb build` can silently fail (exit 0) and then we'd test a stale binary)
|
||||
gb build github.com/matrix-org/dendrite/cmd/dendrite-room-server
|
||||
gb build github.com/matrix-org/dendrite/cmd/roomserver-integration-tests
|
||||
gb build github.com/matrix-org/dendrite/cmd/dendrite-sync-api-server
|
||||
gb build github.com/matrix-org/dendrite/cmd/syncserver-integration-tests
|
||||
gb build github.com/matrix-org/dendrite/cmd/create-account
|
||||
gb build github.com/matrix-org/dendrite/cmd/dendrite-media-api-server
|
||||
gb build github.com/matrix-org/dendrite/cmd/mediaapi-integration-tests
|
||||
gb build github.com/matrix-org/dendrite/cmd/client-api-proxy
|
||||
|
||||
# Create necessary certificates and keys to run dendrite
|
||||
echo "Generating certs..."
|
||||
time openssl req -x509 -newkey rsa:512 -keyout server.key -out server.crt -days 365 -nodes -subj /CN=localhost
|
||||
echo "Installing kafka..."
|
||||
time ./scripts/install-local-kafka.sh
|
||||
|
||||
# Run the integration tests
|
||||
bin/roomserver-integration-tests
|
||||
bin/syncserver-integration-tests
|
||||
bin/mediaapi-integration-tests
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
|
@ -62,10 +63,8 @@ func VerifyAccessToken(req *http.Request, deviceDB DeviceDatabase) (device *auth
|
|||
JSON: jsonerror.UnknownToken("Unknown token"),
|
||||
}
|
||||
} else {
|
||||
resErr = &util.JSONResponse{
|
||||
Code: 500,
|
||||
JSON: jsonerror.Unknown("Failed to check access token"),
|
||||
}
|
||||
jsonErr := httputil.LogThenError(req, err)
|
||||
resErr = &jsonErr
|
||||
}
|
||||
}
|
||||
return
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ package accounts
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
const filterSchema = `
|
||||
|
|
@ -38,12 +40,16 @@ CREATE INDEX IF NOT EXISTS account_filter_localpart ON account_filter(localpart)
|
|||
const selectFilterSQL = "" +
|
||||
"SELECT filter FROM account_filter WHERE localpart = $1 AND id = $2"
|
||||
|
||||
const selectFilterIDByContentSQL = "" +
|
||||
"SELECT id FROM account_filter WHERE localpart = $1 AND filter = $2"
|
||||
|
||||
const insertFilterSQL = "" +
|
||||
"INSERT INTO account_filter (filter, id, localpart) VALUES ($1, DEFAULT, $2) RETURNING id"
|
||||
|
||||
type filterStatements struct {
|
||||
selectFilterStmt *sql.Stmt
|
||||
insertFilterStmt *sql.Stmt
|
||||
selectFilterStmt *sql.Stmt
|
||||
selectFilterIDByContentStmt *sql.Stmt
|
||||
insertFilterStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func (s *filterStatements) prepare(db *sql.DB) (err error) {
|
||||
|
|
@ -54,6 +60,9 @@ func (s *filterStatements) prepare(db *sql.DB) (err error) {
|
|||
if s.selectFilterStmt, err = db.Prepare(selectFilterSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectFilterIDByContentStmt, err = db.Prepare(selectFilterIDByContentSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.insertFilterStmt, err = db.Prepare(insertFilterSQL); err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -62,14 +71,37 @@ func (s *filterStatements) prepare(db *sql.DB) (err error) {
|
|||
|
||||
func (s *filterStatements) selectFilter(
|
||||
ctx context.Context, localpart string, filterID string,
|
||||
) (filter string, err error) {
|
||||
) (filter []byte, err error) {
|
||||
err = s.selectFilterStmt.QueryRowContext(ctx, localpart, filterID).Scan(&filter)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *filterStatements) insertFilter(
|
||||
ctx context.Context, filter string, localpart string,
|
||||
) (pos string, err error) {
|
||||
err = s.insertFilterStmt.QueryRowContext(ctx, filter, localpart).Scan(&pos)
|
||||
ctx context.Context, filter []byte, localpart string,
|
||||
) (filterID string, err error) {
|
||||
var existingFilterID string
|
||||
|
||||
// This can result in a race condition when two clients try to insert the
|
||||
// same filter and localpart at the same time, however this is not a
|
||||
// problem as both calls will result in the same filterID
|
||||
filterJSON, err := gomatrixserverlib.CanonicalJSON(filter)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Check if filter already exists in the database
|
||||
err = s.selectFilterIDByContentStmt.QueryRowContext(ctx,
|
||||
localpart, filterJSON).Scan(&existingFilterID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// If it does, return the existing ID
|
||||
if len(existingFilterID) != 0 {
|
||||
return existingFilterID, err
|
||||
}
|
||||
|
||||
// Otherwise insert the filter and return the new ID
|
||||
err = s.insertFilterStmt.QueryRowContext(ctx, filterJSON, localpart).
|
||||
Scan(&filterID)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,22 +321,25 @@ func (d *Database) GetThreePIDsForLocalpart(
|
|||
}
|
||||
|
||||
// GetFilter looks up the filter associated with a given local user and filter ID.
|
||||
// Returns an error if no such filter exists or if there was an error taling to the database.
|
||||
// Returns a filter represented as a byte slice. Otherwise returns an error if
|
||||
// no such filter exists or if there was an error talking to the database.
|
||||
func (d *Database) GetFilter(
|
||||
ctx context.Context, localpart string, filterID string,
|
||||
) (string, error) {
|
||||
) ([]byte, error) {
|
||||
return d.filter.selectFilter(ctx, localpart, filterID)
|
||||
}
|
||||
|
||||
// PutFilter puts the passed filter into the database.
|
||||
// Returns an error if something goes wrong.
|
||||
// Returns the filterID as a string. Otherwise returns an error if something
|
||||
// goes wrong.
|
||||
func (d *Database) PutFilter(
|
||||
ctx context.Context, localpart, filter string,
|
||||
ctx context.Context, localpart string, filter []byte,
|
||||
) (string, error) {
|
||||
return d.filter.insertFilter(ctx, filter, localpart)
|
||||
}
|
||||
|
||||
// CheckAccountAvailability checks if the username/localpart is already present in the database.
|
||||
// CheckAccountAvailability checks if the username/localpart is already present
|
||||
// in the database.
|
||||
// If the DB returns sql.ErrNoRows the Localpart isn't taken.
|
||||
func (d *Database) CheckAccountAvailability(ctx context.Context, localpart string) (bool, error) {
|
||||
_, err := d.accounts.selectAccountByLocalpart(ctx, localpart)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ CREATE TABLE IF NOT EXISTS device_devices (
|
|||
-- migration to different domain names easier.
|
||||
localpart TEXT NOT NULL,
|
||||
-- When this devices was first recognised on the network, as a unix timestamp (ms resolution).
|
||||
created_ts BIGINT NOT NULL
|
||||
created_ts BIGINT NOT NULL,
|
||||
-- The display name, human friendlier than device_id and updatable
|
||||
display_name TEXT
|
||||
-- TODO: device keys, device display names, last used ts and IP address?, token restrictions (if 3rd-party OAuth app)
|
||||
);
|
||||
|
||||
|
|
@ -49,16 +51,19 @@ CREATE UNIQUE INDEX IF NOT EXISTS device_localpart_id_idx ON device_devices(loca
|
|||
`
|
||||
|
||||
const insertDeviceSQL = "" +
|
||||
"INSERT INTO device_devices(device_id, localpart, access_token, created_ts) VALUES ($1, $2, $3, $4)"
|
||||
"INSERT INTO device_devices(device_id, localpart, access_token, created_ts, display_name) VALUES ($1, $2, $3, $4, $5)"
|
||||
|
||||
const selectDeviceByTokenSQL = "" +
|
||||
"SELECT device_id, localpart FROM device_devices WHERE access_token = $1"
|
||||
|
||||
const selectDeviceByIDSQL = "" +
|
||||
"SELECT created_ts FROM device_devices WHERE localpart = $1 and device_id = $2"
|
||||
"SELECT display_name FROM device_devices WHERE localpart = $1 and device_id = $2"
|
||||
|
||||
const selectDevicesByLocalpartSQL = "" +
|
||||
"SELECT device_id FROM device_devices WHERE localpart = $1"
|
||||
"SELECT device_id, display_name FROM device_devices WHERE localpart = $1"
|
||||
|
||||
const updateDeviceNameSQL = "" +
|
||||
"UPDATE device_devices SET display_name = $1 WHERE localpart = $2 AND device_id = $3"
|
||||
|
||||
const deleteDeviceSQL = "" +
|
||||
"DELETE FROM device_devices WHERE device_id = $1 AND localpart = $2"
|
||||
|
|
@ -66,13 +71,12 @@ const deleteDeviceSQL = "" +
|
|||
const deleteDevicesByLocalpartSQL = "" +
|
||||
"DELETE FROM device_devices WHERE localpart = $1"
|
||||
|
||||
// TODO: List devices?
|
||||
|
||||
type devicesStatements struct {
|
||||
insertDeviceStmt *sql.Stmt
|
||||
selectDeviceByTokenStmt *sql.Stmt
|
||||
selectDeviceByIDStmt *sql.Stmt
|
||||
selectDevicesByLocalpartStmt *sql.Stmt
|
||||
updateDeviceNameStmt *sql.Stmt
|
||||
deleteDeviceStmt *sql.Stmt
|
||||
deleteDevicesByLocalpartStmt *sql.Stmt
|
||||
serverName gomatrixserverlib.ServerName
|
||||
|
|
@ -95,6 +99,9 @@ func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerN
|
|||
if s.selectDevicesByLocalpartStmt, err = db.Prepare(selectDevicesByLocalpartSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.updateDeviceNameStmt, err = db.Prepare(updateDeviceNameSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteDeviceStmt, err = db.Prepare(deleteDeviceSQL); err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -110,10 +117,11 @@ func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerN
|
|||
// Returns the device on success.
|
||||
func (s *devicesStatements) insertDevice(
|
||||
ctx context.Context, txn *sql.Tx, id, localpart, accessToken string,
|
||||
displayName *string,
|
||||
) (*authtypes.Device, error) {
|
||||
createdTimeMS := time.Now().UnixNano() / 1000000
|
||||
stmt := common.TxStmt(txn, s.insertDeviceStmt)
|
||||
if _, err := stmt.ExecContext(ctx, id, localpart, accessToken, createdTimeMS); err != nil {
|
||||
if _, err := stmt.ExecContext(ctx, id, localpart, accessToken, createdTimeMS, displayName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &authtypes.Device{
|
||||
|
|
@ -139,6 +147,14 @@ func (s *devicesStatements) deleteDevicesByLocalpart(
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *devicesStatements) updateDeviceName(
|
||||
ctx context.Context, txn *sql.Tx, localpart, deviceID string, displayName *string,
|
||||
) error {
|
||||
stmt := common.TxStmt(txn, s.updateDeviceNameStmt)
|
||||
_, err := stmt.ExecContext(ctx, displayName, localpart, deviceID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *devicesStatements) selectDeviceByToken(
|
||||
ctx context.Context, accessToken string,
|
||||
) (*authtypes.Device, error) {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ func (d *Database) GetDevicesByLocalpart(
|
|||
// Returns the device on success.
|
||||
func (d *Database) CreateDevice(
|
||||
ctx context.Context, localpart string, deviceID *string, accessToken string,
|
||||
displayName *string,
|
||||
) (dev *authtypes.Device, returnErr error) {
|
||||
if deviceID != nil {
|
||||
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||
|
|
@ -84,7 +85,7 @@ func (d *Database) CreateDevice(
|
|||
return err
|
||||
}
|
||||
|
||||
dev, err = d.devices.insertDevice(ctx, txn, *deviceID, localpart, accessToken)
|
||||
dev, err = d.devices.insertDevice(ctx, txn, *deviceID, localpart, accessToken, displayName)
|
||||
return err
|
||||
})
|
||||
} else {
|
||||
|
|
@ -99,7 +100,7 @@ func (d *Database) CreateDevice(
|
|||
|
||||
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||
var err error
|
||||
dev, err = d.devices.insertDevice(ctx, txn, newDeviceID, localpart, accessToken)
|
||||
dev, err = d.devices.insertDevice(ctx, txn, newDeviceID, localpart, accessToken, displayName)
|
||||
return err
|
||||
})
|
||||
if returnErr == nil {
|
||||
|
|
@ -110,6 +111,16 @@ func (d *Database) CreateDevice(
|
|||
return
|
||||
}
|
||||
|
||||
// UpdateDevice updates the given device with the display name.
|
||||
// Returns SQL error if there are problems and nil on success.
|
||||
func (d *Database) UpdateDevice(
|
||||
ctx context.Context, localpart, deviceID string, displayName *string,
|
||||
) error {
|
||||
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||
return d.devices.updateDeviceName(ctx, txn, localpart, deviceID, displayName)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveDevice revokes a device by deleting the entry in the database
|
||||
// matching with the given device ID and user ID localpart
|
||||
// If the device doesn't exist, it will not return an error
|
||||
|
|
|
|||
|
|
@ -96,11 +96,7 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := s.db.UpdateMemberships(context.TODO(), events, output.NewRoomEvent.RemovesStateEventIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.db.UpdateMemberships(context.TODO(), events, output.NewRoomEvent.RemovesStateEventIDs)
|
||||
}
|
||||
|
||||
// lookupStateEvents looks up the state events that are added by a new event.
|
||||
|
|
|
|||
|
|
@ -67,6 +67,18 @@ func NotFound(msg string) *MatrixError {
|
|||
return &MatrixError{"M_NOT_FOUND", msg}
|
||||
}
|
||||
|
||||
// MissingArgument is an error when the client tries to access a resource
|
||||
// without providing an argument that is required.
|
||||
func MissingArgument(msg string) *MatrixError {
|
||||
return &MatrixError{"M_MISSING_ARGUMENT", msg}
|
||||
}
|
||||
|
||||
// InvalidArgumentValue is an error when the client tries to provide an
|
||||
// invalid value for a valid argument
|
||||
func InvalidArgumentValue(msg string) *MatrixError {
|
||||
return &MatrixError{"M_INVALID_ARGUMENT_VALUE", msg}
|
||||
}
|
||||
|
||||
// MissingToken is an error when the client tries to access a resource which
|
||||
// requires authentication without supplying credentials.
|
||||
func MissingToken(msg string) *MatrixError {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
|
|
@ -33,6 +32,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ package routing
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
|
|
@ -35,6 +36,10 @@ type devicesJSON struct {
|
|||
Devices []deviceJSON `json:"devices"`
|
||||
}
|
||||
|
||||
type deviceUpdateJSON struct {
|
||||
DisplayName *string `json:"display_name"`
|
||||
}
|
||||
|
||||
// GetDeviceByID handles /device/{deviceID}
|
||||
func GetDeviceByID(
|
||||
req *http.Request, deviceDB *devices.Database, device *authtypes.Device,
|
||||
|
|
@ -75,7 +80,7 @@ func GetDevicesByLocalpart(
|
|||
}
|
||||
|
||||
ctx := req.Context()
|
||||
devices, err := deviceDB.GetDevicesByLocalpart(ctx, localpart)
|
||||
deviceList, err := deviceDB.GetDevicesByLocalpart(ctx, localpart)
|
||||
|
||||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
|
|
@ -83,7 +88,7 @@ func GetDevicesByLocalpart(
|
|||
|
||||
res := devicesJSON{}
|
||||
|
||||
for _, dev := range devices {
|
||||
for _, dev := range deviceList {
|
||||
res.Devices = append(res.Devices, deviceJSON{
|
||||
DeviceID: dev.ID,
|
||||
UserID: dev.UserID,
|
||||
|
|
@ -95,3 +100,56 @@ func GetDevicesByLocalpart(
|
|||
JSON: res,
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateDeviceByID handles PUT on /devices/{deviceID}
|
||||
func UpdateDeviceByID(
|
||||
req *http.Request, deviceDB *devices.Database, device *authtypes.Device,
|
||||
deviceID string,
|
||||
) util.JSONResponse {
|
||||
if req.Method != "PUT" {
|
||||
return util.JSONResponse{
|
||||
Code: 405,
|
||||
JSON: jsonerror.NotFound("Bad Method"),
|
||||
}
|
||||
}
|
||||
|
||||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
ctx := req.Context()
|
||||
dev, err := deviceDB.GetDeviceByID(ctx, localpart, deviceID)
|
||||
if err == sql.ErrNoRows {
|
||||
return util.JSONResponse{
|
||||
Code: 404,
|
||||
JSON: jsonerror.NotFound("Unknown device"),
|
||||
}
|
||||
} else if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
if dev.UserID != device.UserID {
|
||||
return util.JSONResponse{
|
||||
Code: 403,
|
||||
JSON: jsonerror.Forbidden("device not owned by current user"),
|
||||
}
|
||||
}
|
||||
|
||||
defer req.Body.Close() // nolint: errcheck
|
||||
|
||||
payload := deviceUpdateJSON{}
|
||||
|
||||
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
if err := deviceDB.UpdateDevice(ctx, localpart, deviceID, payload.DisplayName); err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func GetFilter(
|
|||
}
|
||||
}
|
||||
filter := gomatrix.Filter{}
|
||||
err = json.Unmarshal([]byte(res), &filter)
|
||||
err = json.Unmarshal(res, &filter)
|
||||
if err != nil {
|
||||
httputil.LogThenError(req, err)
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ func PutFilter(
|
|||
}
|
||||
}
|
||||
|
||||
filterID, err := accountDB.PutFilter(req.Context(), localpart, string(filterArray))
|
||||
filterID, err := accountDB.PutFilter(req.Context(), localpart, filterArray)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,8 +38,9 @@ type flow struct {
|
|||
}
|
||||
|
||||
type passwordRequest struct {
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
InitialDisplayName *string `json:"initial_device_display_name"`
|
||||
}
|
||||
|
||||
type loginResponse struct {
|
||||
|
|
@ -119,7 +120,7 @@ func Login(
|
|||
|
||||
// TODO: Use the device ID in the request
|
||||
dev, err := deviceDB.CreateDevice(
|
||||
req.Context(), acc.Localpart, nil, token,
|
||||
req.Context(), acc.Localpart, nil, token, r.InitialDisplayName,
|
||||
)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ type response struct {
|
|||
// GetMemberships implements GET /rooms/{roomId}/members
|
||||
func GetMemberships(
|
||||
req *http.Request, device *authtypes.Device, roomID string, joinedOnly bool,
|
||||
cfg config.Dendrite,
|
||||
_ config.Dendrite,
|
||||
queryAPI api.RoomserverQueryAPI,
|
||||
) util.JSONResponse {
|
||||
queryReq := api.QueryMembershipsForRoomRequest{
|
||||
|
|
|
|||
|
|
@ -31,19 +31,6 @@ import (
|
|||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
type profileResponse struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
DisplayName string `json:"displayname"`
|
||||
}
|
||||
|
||||
type avatarURL struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}
|
||||
|
||||
type displayName struct {
|
||||
DisplayName string `json:"displayname"`
|
||||
}
|
||||
|
||||
// GetProfile implements GET /profile/{userID}
|
||||
func GetProfile(
|
||||
req *http.Request, accountDB *accounts.Database, userID string,
|
||||
|
|
@ -63,7 +50,7 @@ func GetProfile(
|
|||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
res := profileResponse{
|
||||
res := common.ProfileResponse{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
DisplayName: profile.DisplayName,
|
||||
}
|
||||
|
|
@ -86,7 +73,7 @@ func GetAvatarURL(
|
|||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
res := avatarURL{
|
||||
res := common.AvatarURL{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
}
|
||||
return util.JSONResponse{
|
||||
|
|
@ -110,7 +97,7 @@ func SetAvatarURL(
|
|||
|
||||
changedKey := "avatar_url"
|
||||
|
||||
var r avatarURL
|
||||
var r common.AvatarURL
|
||||
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
|
@ -178,7 +165,7 @@ func GetDisplayName(
|
|||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
res := displayName{
|
||||
res := common.DisplayName{
|
||||
DisplayName: profile.DisplayName,
|
||||
}
|
||||
return util.JSONResponse{
|
||||
|
|
@ -202,7 +189,7 @@ func SetDisplayName(
|
|||
|
||||
changedKey := "displayname"
|
||||
|
||||
var r displayName
|
||||
var r common.DisplayName
|
||||
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||
|
|
@ -37,6 +36,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -60,6 +60,8 @@ type registerRequest struct {
|
|||
Admin bool `json:"admin"`
|
||||
// user-interactive auth params
|
||||
Auth authDict `json:"auth"`
|
||||
|
||||
InitialDisplayName *string `json:"initial_device_display_name"`
|
||||
}
|
||||
|
||||
type authDict struct {
|
||||
|
|
@ -210,10 +212,10 @@ func Register(
|
|||
return util.MessageResponse(403, "HMAC incorrect")
|
||||
}
|
||||
|
||||
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password)
|
||||
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password, r.InitialDisplayName)
|
||||
case authtypes.LoginTypeDummy:
|
||||
// there is nothing to do
|
||||
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password)
|
||||
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password, r.InitialDisplayName)
|
||||
default:
|
||||
return util.JSONResponse{
|
||||
Code: 501,
|
||||
|
|
@ -270,10 +272,10 @@ func LegacyRegister(
|
|||
return util.MessageResponse(403, "HMAC incorrect")
|
||||
}
|
||||
|
||||
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password)
|
||||
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password, nil)
|
||||
case authtypes.LoginTypeDummy:
|
||||
// there is nothing to do
|
||||
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password)
|
||||
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password, nil)
|
||||
default:
|
||||
return util.JSONResponse{
|
||||
Code: 501,
|
||||
|
|
@ -287,6 +289,7 @@ func completeRegistration(
|
|||
accountDB *accounts.Database,
|
||||
deviceDB *devices.Database,
|
||||
username, password string,
|
||||
displayName *string,
|
||||
) util.JSONResponse {
|
||||
if username == "" {
|
||||
return util.JSONResponse{
|
||||
|
|
@ -318,7 +321,7 @@ func completeRegistration(
|
|||
}
|
||||
|
||||
// // TODO: Use the device ID in the request.
|
||||
dev, err := deviceDB.CreateDevice(ctx, username, nil, token)
|
||||
dev, err := deviceDB.CreateDevice(ctx, username, nil, token, displayName)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: 500,
|
||||
|
|
|
|||
|
|
@ -284,12 +284,8 @@ func Setup(
|
|||
).Methods("PUT", "OPTIONS")
|
||||
|
||||
r0mux.Handle("/voip/turnServer",
|
||||
common.MakeExternalAPI("turn_server", func(req *http.Request) util.JSONResponse {
|
||||
// TODO: Return credentials for a turn server if one is configured.
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
common.MakeAuthAPI("turn_server", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||
return RequestTurnServer(req, device, cfg)
|
||||
}),
|
||||
).Methods("GET")
|
||||
|
||||
|
|
@ -368,6 +364,13 @@ func Setup(
|
|||
}),
|
||||
).Methods("GET")
|
||||
|
||||
r0mux.Handle("/devices/{deviceID}",
|
||||
common.MakeAuthAPI("device_data", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||
vars := mux.Vars(req)
|
||||
return UpdateDeviceByID(req, deviceDB, device, vars["deviceID"])
|
||||
}),
|
||||
).Methods("PUT", "OPTIONS")
|
||||
|
||||
// Stub implementations for sytest
|
||||
r0mux.Handle("/events",
|
||||
common.MakeExternalAPI("events", func(req *http.Request) util.JSONResponse {
|
||||
|
|
|
|||
79
src/github.com/matrix-org/dendrite/clientapi/routing/voip.go
Normal file
79
src/github.com/matrix-org/dendrite/clientapi/routing/voip.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2017 Michael Telatysnki <7t3chguy@gmail.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// RequestTurnServer implements:
|
||||
// GET /voip/turnServer
|
||||
func RequestTurnServer(req *http.Request, device *authtypes.Device, cfg config.Dendrite) util.JSONResponse {
|
||||
turnConfig := cfg.TURN
|
||||
|
||||
// TODO Guest Support
|
||||
if len(turnConfig.URIs) == 0 || turnConfig.UserLifetime == "" {
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// Duration checked at startup, err not possible
|
||||
duration, _ := time.ParseDuration(turnConfig.UserLifetime)
|
||||
|
||||
resp := gomatrix.RespTurnServer{
|
||||
URIs: turnConfig.URIs,
|
||||
TTL: int(duration.Seconds()),
|
||||
}
|
||||
|
||||
if turnConfig.SharedSecret != "" {
|
||||
expiry := time.Now().Add(duration).Unix()
|
||||
mac := hmac.New(sha1.New, []byte(turnConfig.SharedSecret))
|
||||
_, err := mac.Write([]byte(resp.Username))
|
||||
|
||||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
resp.Username = fmt.Sprintf("%d:%s", expiry, device.UserID)
|
||||
resp.Password = base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
} else if turnConfig.Username != "" && turnConfig.Password != "" {
|
||||
resp.Username = turnConfig.Username
|
||||
resp.Password = turnConfig.Password
|
||||
} else {
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: resp,
|
||||
}
|
||||
}
|
||||
|
|
@ -355,9 +355,5 @@ func emit3PIDInviteEvent(
|
|||
return err
|
||||
}
|
||||
|
||||
if err := producer.SendEvents(ctx, []gomatrixserverlib.Event{*event}, cfg.Matrix.ServerName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return producer.SendEvents(ctx, []gomatrixserverlib.Event{*event}, cfg.Matrix.ServerName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ func main() {
|
|||
}
|
||||
|
||||
device, err := deviceDB.CreateDevice(
|
||||
context.Background(), *username, nil, *accessToken,
|
||||
context.Background(), *username, nil, *accessToken, nil,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
|
|
|
|||
|
|
@ -98,13 +98,7 @@ func main() {
|
|||
log.Panicf("Failed to setup key database(%q): %s", cfg.Database.ServerKey, err.Error())
|
||||
}
|
||||
|
||||
keyRing := gomatrixserverlib.KeyRing{
|
||||
KeyFetchers: []gomatrixserverlib.KeyFetcher{
|
||||
// TODO: Use perspective key fetchers for production.
|
||||
&gomatrixserverlib.DirectKeyFetcher{Client: federation.Client},
|
||||
},
|
||||
KeyDatabase: keyDB,
|
||||
}
|
||||
keyRing := keydb.CreateKeyRing(federation.Client, keyDB)
|
||||
|
||||
kafkaConsumer, err := sarama.NewConsumer(cfg.Kafka.Addresses, nil)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"flag"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -194,18 +195,26 @@ func (m *monolith) setupFederation() {
|
|||
m.cfg.Matrix.ServerName, m.cfg.Matrix.KeyID, m.cfg.Matrix.PrivateKey,
|
||||
)
|
||||
|
||||
m.keyRing = gomatrixserverlib.KeyRing{
|
||||
KeyFetchers: []gomatrixserverlib.KeyFetcher{
|
||||
// TODO: Use perspective key fetchers for production.
|
||||
&gomatrixserverlib.DirectKeyFetcher{Client: m.federation.Client},
|
||||
},
|
||||
KeyDatabase: m.keyDB,
|
||||
}
|
||||
m.keyRing = keydb.CreateKeyRing(m.federation.Client, m.keyDB)
|
||||
}
|
||||
|
||||
func (m *monolith) setupKafka() {
|
||||
if m.cfg.Kafka.UseNaffka {
|
||||
naff, err := naffka.New(&naffka.MemoryDatabase{})
|
||||
db, err := sql.Open("postgres", string(m.cfg.Database.Naffka))
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
log.ErrorKey: err,
|
||||
}).Panic("Failed to open naffka database")
|
||||
}
|
||||
|
||||
naffkaDB, err := naffka.NewPostgresqlDatabase(db)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
log.ErrorKey: err,
|
||||
}).Panic("Failed to setup naffka database")
|
||||
}
|
||||
|
||||
naff, err := naffka.New(naffkaDB)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
log.ErrorKey: err,
|
||||
|
|
@ -336,7 +345,7 @@ func (m *monolith) setupAPIs() {
|
|||
|
||||
syncapi_routing.Setup(m.api, syncapi_sync.NewRequestPool(
|
||||
m.syncAPIDB, m.syncAPINotifier, m.accountDB,
|
||||
), m.deviceDB)
|
||||
), m.syncAPIDB, m.deviceDB)
|
||||
|
||||
federationapi_routing.Setup(
|
||||
m.api, *m.cfg, m.queryAPI, m.aliasAPI, m.roomServerProducer, m.keyRing, m.federation,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
_ "net/http/pprof"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/roomserver/alias"
|
||||
|
|
@ -28,6 +27,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/roomserver/query"
|
||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
sarama "gopkg.in/Shopify/sarama.v1"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ func main() {
|
|||
log.Info("Starting sync server on ", cfg.Listen.SyncAPI)
|
||||
|
||||
api := mux.NewRouter()
|
||||
routing.Setup(api, sync.NewRequestPool(db, n, adb), deviceDB)
|
||||
routing.Setup(api, sync.NewRequestPool(db, n, adb), db, deviceDB)
|
||||
common.SetupHTTPAPI(http.DefaultServeMux, api)
|
||||
|
||||
log.Fatal(http.ListenAndServe(string(cfg.Listen.SyncAPI), nil))
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ var timeout time.Duration
|
|||
|
||||
var port = 10000
|
||||
|
||||
func startMediaAPI(suffix string, dynamicThumbnails bool) (*exec.Cmd, chan error, string, *exec.Cmd, chan error, string, string) {
|
||||
func startMediaAPI(suffix string, dynamicThumbnails bool) (*exec.Cmd, chan error, *exec.Cmd, string, string) {
|
||||
dir, err := ioutil.TempDir("", serverType+"-server-test"+suffix)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
@ -107,7 +107,7 @@ func startMediaAPI(suffix string, dynamicThumbnails bool) (*exec.Cmd, chan error
|
|||
testDatabaseName + suffix,
|
||||
}
|
||||
|
||||
proxyCmd, proxyCmdChan := test.StartProxy(proxyAddr, cfg)
|
||||
proxyCmd, _ := test.StartProxy(proxyAddr, cfg)
|
||||
|
||||
test.InitDatabase(
|
||||
postgresDatabase,
|
||||
|
|
@ -121,7 +121,7 @@ func startMediaAPI(suffix string, dynamicThumbnails bool) (*exec.Cmd, chan error
|
|||
)
|
||||
|
||||
fmt.Printf("==TESTSERVER== STARTED %v -> %v : %v\n", proxyAddr, cfg.Listen.MediaAPI, dir)
|
||||
return cmd, cmdChan, string(cfg.Listen.MediaAPI), proxyCmd, proxyCmdChan, proxyAddr, dir
|
||||
return cmd, cmdChan, proxyCmd, proxyAddr, dir
|
||||
}
|
||||
|
||||
func cleanUpServer(cmd *exec.Cmd, dir string) {
|
||||
|
|
@ -145,7 +145,7 @@ func main() {
|
|||
}
|
||||
|
||||
// create server1 with only pre-generated thumbnails allowed
|
||||
server1Cmd, server1CmdChan, _, server1ProxyCmd, _, server1ProxyAddr, server1Dir := startMediaAPI("1", false)
|
||||
server1Cmd, server1CmdChan, server1ProxyCmd, server1ProxyAddr, server1Dir := startMediaAPI("1", false)
|
||||
defer cleanUpServer(server1Cmd, server1Dir)
|
||||
defer server1ProxyCmd.Process.Kill() // nolint: errcheck
|
||||
testDownload(server1ProxyAddr, server1ProxyAddr, "doesnotexist", 404, server1CmdChan)
|
||||
|
|
@ -162,7 +162,7 @@ func main() {
|
|||
testThumbnail(64, 64, "crop", server1ProxyAddr, server1CmdChan)
|
||||
|
||||
// create server2 with dynamic thumbnail generation
|
||||
server2Cmd, server2CmdChan, _, server2ProxyCmd, _, server2ProxyAddr, server2Dir := startMediaAPI("2", true)
|
||||
server2Cmd, server2CmdChan, server2ProxyCmd, server2ProxyAddr, server2Dir := startMediaAPI("2", true)
|
||||
defer cleanUpServer(server2Cmd, server2Dir)
|
||||
defer server2ProxyCmd.Process.Kill() // nolint: errcheck
|
||||
testDownload(server2ProxyAddr, server2ProxyAddr, "doesnotexist", 404, server2CmdChan)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
|
|
@ -148,8 +148,30 @@ type Dendrite struct {
|
|||
// The PublicRoomsAPI database stores information used to compute the public
|
||||
// room directory. It is only accessed by the PublicRoomsAPI server.
|
||||
PublicRoomsAPI DataSource `yaml:"public_rooms_api"`
|
||||
// The Naffka database is used internally by the naffka library, if used.
|
||||
Naffka DataSource `yaml:"naffka,omitempty"`
|
||||
} `yaml:"database"`
|
||||
|
||||
// TURN Server Config
|
||||
TURN struct {
|
||||
// TODO Guest Support
|
||||
// Whether or not guests can request TURN credentials
|
||||
//AllowGuests bool `yaml:"turn_allow_guests"`
|
||||
// How long the authorization should last
|
||||
UserLifetime string `yaml:"turn_user_lifetime"`
|
||||
// The list of TURN URIs to pass to clients
|
||||
URIs []string `yaml:"turn_uris"`
|
||||
|
||||
// Authorization via Shared Secret
|
||||
// The shared secret from coturn
|
||||
SharedSecret string `yaml:"turn_shared_secret"`
|
||||
|
||||
// Authorization via Static Username & Password
|
||||
// Hardcoded Username and Password
|
||||
Username string `yaml:"turn_username"`
|
||||
Password string `yaml:"turn_password"`
|
||||
}
|
||||
|
||||
// The internal addresses the components will listen on.
|
||||
// These should not be exposed externally as they expose metrics and debugging APIs.
|
||||
Listen struct {
|
||||
|
|
@ -341,10 +363,20 @@ func (config *Dendrite) check(monolithic bool) error {
|
|||
}
|
||||
}
|
||||
|
||||
checkValidDuration := func(key, value string) {
|
||||
if _, err := time.ParseDuration(config.TURN.UserLifetime); err != nil {
|
||||
problems = append(problems, fmt.Sprintf("invalid duration for config key %q: %s", key, value))
|
||||
}
|
||||
}
|
||||
|
||||
checkNotEmpty("matrix.server_name", string(config.Matrix.ServerName))
|
||||
checkNotEmpty("matrix.private_key", string(config.Matrix.PrivateKeyPath))
|
||||
checkNotZero("matrix.federation_certificates", int64(len(config.Matrix.FederationCertificatePaths)))
|
||||
|
||||
if config.TURN.UserLifetime != "" {
|
||||
checkValidDuration("turn.turn_user_lifetime", config.TURN.UserLifetime)
|
||||
}
|
||||
|
||||
checkNotEmpty("media.base_path", string(config.Media.BasePath))
|
||||
checkPositive("media.max_file_size_bytes", int64(*config.Media.MaxFileSizeBytes))
|
||||
checkPositive("media.max_thumbnail_generators", int64(config.Media.MaxThumbnailGenerators))
|
||||
|
|
@ -356,6 +388,8 @@ func (config *Dendrite) check(monolithic bool) error {
|
|||
if !monolithic {
|
||||
problems = append(problems, fmt.Sprintf("naffka can only be used in a monolithic server"))
|
||||
}
|
||||
|
||||
checkNotEmpty("database.naffka", string(config.Database.Naffka))
|
||||
} else {
|
||||
// If we aren't using naffka then we need to have at least one kafka
|
||||
// server to talk to.
|
||||
|
|
|
|||
|
|
@ -48,14 +48,14 @@ func NewDatabase(dataSourceName string) (*Database, error) {
|
|||
func (d *Database) FetchKeys(
|
||||
ctx context.Context,
|
||||
requests map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.Timestamp,
|
||||
) (map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.ServerKeys, error) {
|
||||
) (map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.PublicKeyLookupResult, error) {
|
||||
return d.statements.bulkSelectServerKeys(ctx, requests)
|
||||
}
|
||||
|
||||
// StoreKeys implements gomatrixserverlib.KeyDatabase
|
||||
func (d *Database) StoreKeys(
|
||||
ctx context.Context,
|
||||
keyMap map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.ServerKeys,
|
||||
keyMap map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.PublicKeyLookupResult,
|
||||
) error {
|
||||
// TODO: Inserting all the keys within a single transaction may
|
||||
// be more efficient since the transaction overhead can be quite
|
||||
|
|
|
|||
32
src/github.com/matrix-org/dendrite/common/keydb/keyring.go
Normal file
32
src/github.com/matrix-org/dendrite/common/keydb/keyring.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2017 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keydb
|
||||
|
||||
import "github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
// CreateKeyRing creates and configures a KeyRing object.
|
||||
//
|
||||
// It creates the necessary key fetchers and collects them into a KeyRing
|
||||
// backed by the given KeyDatabase.
|
||||
func CreateKeyRing(client gomatrixserverlib.Client,
|
||||
keyDB gomatrixserverlib.KeyDatabase) gomatrixserverlib.KeyRing {
|
||||
return gomatrixserverlib.KeyRing{
|
||||
KeyFetchers: []gomatrixserverlib.KeyFetcher{
|
||||
// TODO: Use perspective key fetchers for production.
|
||||
&gomatrixserverlib.DirectKeyFetcher{Client: client},
|
||||
},
|
||||
KeyDatabase: keyDB,
|
||||
}
|
||||
}
|
||||
|
|
@ -17,14 +17,13 @@ package keydb
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
const serverKeysSchema = `
|
||||
-- A cache of server keys downloaded from remote servers.
|
||||
-- A cache of signing keys downloaded from remote servers.
|
||||
CREATE TABLE IF NOT EXISTS keydb_server_keys (
|
||||
-- The name of the matrix server the key is for.
|
||||
server_name TEXT NOT NULL,
|
||||
|
|
@ -33,10 +32,14 @@ CREATE TABLE IF NOT EXISTS keydb_server_keys (
|
|||
-- Combined server name and key ID separated by the ASCII unit separator
|
||||
-- to make it easier to run bulk queries.
|
||||
server_name_and_key_id TEXT NOT NULL,
|
||||
-- When the keys are valid until as a millisecond timestamp.
|
||||
-- When the key is valid until as a millisecond timestamp.
|
||||
-- 0 if this is an expired key (in which case expired_ts will be non-zero)
|
||||
valid_until_ts BIGINT NOT NULL,
|
||||
-- The raw JSON for the server key.
|
||||
server_key_json TEXT NOT NULL,
|
||||
-- When the key expired as a millisecond timestamp.
|
||||
-- 0 if this is an active key (in which case valid_until_ts will be non-zero)
|
||||
expired_ts BIGINT NOT NULL,
|
||||
-- The base64-encoded public key.
|
||||
server_key TEXT NOT NULL,
|
||||
CONSTRAINT keydb_server_keys_unique UNIQUE (server_name, server_key_id)
|
||||
);
|
||||
|
||||
|
|
@ -44,15 +47,16 @@ CREATE INDEX IF NOT EXISTS keydb_server_name_and_key_id ON keydb_server_keys (se
|
|||
`
|
||||
|
||||
const bulkSelectServerKeysSQL = "" +
|
||||
"SELECT server_name, server_key_id, server_key_json FROM keydb_server_keys" +
|
||||
"SELECT server_name, server_key_id, valid_until_ts, expired_ts, " +
|
||||
" server_key FROM keydb_server_keys" +
|
||||
" WHERE server_name_and_key_id = ANY($1)"
|
||||
|
||||
const upsertServerKeysSQL = "" +
|
||||
"INSERT INTO keydb_server_keys (server_name, server_key_id," +
|
||||
" server_name_and_key_id, valid_until_ts, server_key_json)" +
|
||||
" VALUES ($1, $2, $3, $4, $5)" +
|
||||
" server_name_and_key_id, valid_until_ts, expired_ts, server_key)" +
|
||||
" VALUES ($1, $2, $3, $4, $5, $6)" +
|
||||
" ON CONFLICT ON CONSTRAINT keydb_server_keys_unique" +
|
||||
" DO UPDATE SET valid_until_ts = $4, server_key_json = $5"
|
||||
" DO UPDATE SET valid_until_ts = $4, expired_ts = $5, server_key = $6"
|
||||
|
||||
type serverKeyStatements struct {
|
||||
bulkSelectServerKeysStmt *sql.Stmt
|
||||
|
|
@ -76,7 +80,7 @@ func (s *serverKeyStatements) prepare(db *sql.DB) (err error) {
|
|||
func (s *serverKeyStatements) bulkSelectServerKeys(
|
||||
ctx context.Context,
|
||||
requests map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.Timestamp,
|
||||
) (map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.ServerKeys, error) {
|
||||
) (map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.PublicKeyLookupResult, error) {
|
||||
var nameAndKeyIDs []string
|
||||
for request := range requests {
|
||||
nameAndKeyIDs = append(nameAndKeyIDs, nameAndKeyID(request))
|
||||
|
|
@ -87,23 +91,30 @@ func (s *serverKeyStatements) bulkSelectServerKeys(
|
|||
return nil, err
|
||||
}
|
||||
defer rows.Close() // nolint: errcheck
|
||||
results := map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.ServerKeys{}
|
||||
results := map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.PublicKeyLookupResult{}
|
||||
for rows.Next() {
|
||||
var serverName string
|
||||
var keyID string
|
||||
var keyJSON []byte
|
||||
if err := rows.Scan(&serverName, &keyID, &keyJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var serverKeys gomatrixserverlib.ServerKeys
|
||||
if err := json.Unmarshal(keyJSON, &serverKeys); err != nil {
|
||||
var key string
|
||||
var validUntilTS int64
|
||||
var expiredTS int64
|
||||
if err = rows.Scan(&serverName, &keyID, &validUntilTS, &expiredTS, &key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := gomatrixserverlib.PublicKeyRequest{
|
||||
ServerName: gomatrixserverlib.ServerName(serverName),
|
||||
KeyID: gomatrixserverlib.KeyID(keyID),
|
||||
}
|
||||
results[r] = serverKeys
|
||||
vk := gomatrixserverlib.VerifyKey{}
|
||||
err = vk.Key.Decode(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[r] = gomatrixserverlib.PublicKeyLookupResult{
|
||||
VerifyKey: vk,
|
||||
ValidUntilTS: gomatrixserverlib.Timestamp(validUntilTS),
|
||||
ExpiredTS: gomatrixserverlib.Timestamp(expiredTS),
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
|
@ -111,19 +122,16 @@ func (s *serverKeyStatements) bulkSelectServerKeys(
|
|||
func (s *serverKeyStatements) upsertServerKeys(
|
||||
ctx context.Context,
|
||||
request gomatrixserverlib.PublicKeyRequest,
|
||||
keys gomatrixserverlib.ServerKeys,
|
||||
key gomatrixserverlib.PublicKeyLookupResult,
|
||||
) error {
|
||||
keyJSON, err := json.Marshal(keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.upsertServerKeysStmt.ExecContext(
|
||||
_, err := s.upsertServerKeysStmt.ExecContext(
|
||||
ctx,
|
||||
string(request.ServerName),
|
||||
string(request.KeyID),
|
||||
nameAndKeyID(request),
|
||||
int64(keys.ValidUntilTS),
|
||||
keyJSON,
|
||||
key.ValidUntilTS,
|
||||
key.ExpiredTS,
|
||||
key.Key.Encode(),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dugong"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type utcFormatter struct {
|
||||
|
|
|
|||
|
|
@ -113,10 +113,7 @@ func WriteConfig(cfg *config.Dendrite, configDir string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ioutil.WriteFile(filepath.Join(configDir, ConfigFile), data, 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return ioutil.WriteFile(filepath.Join(configDir, ConfigFile), data, 0666)
|
||||
}
|
||||
|
||||
// NewMatrixKey generates a new ed25519 matrix server key and writes it to a file.
|
||||
|
|
|
|||
|
|
@ -20,3 +20,19 @@ type AccountData struct {
|
|||
RoomID string `json:"room_id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ProfileResponse is a struct containing all known user profile data
|
||||
type ProfileResponse struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
DisplayName string `json:"displayname"`
|
||||
}
|
||||
|
||||
// AvatarURL is a struct containing only the URL to a user's avatar
|
||||
type AvatarURL struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}
|
||||
|
||||
// DisplayName is a struct containing only a user's display name
|
||||
type DisplayName struct {
|
||||
DisplayName string `json:"displayname"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ import (
|
|||
func GetEvent(
|
||||
ctx context.Context,
|
||||
request *gomatrixserverlib.FederationRequest,
|
||||
cfg config.Dendrite,
|
||||
_ config.Dendrite,
|
||||
query api.RoomserverQueryAPI,
|
||||
now time.Time,
|
||||
keys gomatrixserverlib.KeyRing,
|
||||
_ time.Time,
|
||||
_ gomatrixserverlib.KeyRing,
|
||||
eventID string,
|
||||
) util.JSONResponse {
|
||||
var authResponse api.QueryServerAllowedToSeeEventResponse
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ func localKeys(cfg config.Dendrite, validUntil time.Time) (*gomatrixserverlib.Se
|
|||
var keys gomatrixserverlib.ServerKeys
|
||||
|
||||
keys.ServerName = cfg.Matrix.ServerName
|
||||
keys.FromServer = cfg.Matrix.ServerName
|
||||
|
||||
publicKey := cfg.Matrix.PrivateKey.Public().(ed25519.PublicKey)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2017 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// GetProfile implements GET /_matrix/federation/v1/query/profile
|
||||
func GetProfile(
|
||||
httpReq *http.Request,
|
||||
accountDB *accounts.Database,
|
||||
cfg config.Dendrite,
|
||||
) util.JSONResponse {
|
||||
userID, field := httpReq.FormValue("user_id"), httpReq.FormValue("field")
|
||||
|
||||
// httpReq.FormValue will return an empty string if value is not found
|
||||
if userID == "" {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.MissingArgument("The request body did not contain required argument 'user_id'."),
|
||||
}
|
||||
}
|
||||
|
||||
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
if domain != cfg.Matrix.ServerName {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
profile, err := accountDB.GetProfileByLocalpart(httpReq.Context(), localpart)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(httpReq, err)
|
||||
}
|
||||
|
||||
var res interface{}
|
||||
code := 200
|
||||
|
||||
if field != "" {
|
||||
switch field {
|
||||
case "displayname":
|
||||
res = common.DisplayName{
|
||||
DisplayName: profile.DisplayName,
|
||||
}
|
||||
case "avatar_url":
|
||||
res = common.AvatarURL{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
}
|
||||
default:
|
||||
code = 400
|
||||
res = jsonerror.InvalidArgumentValue("The request body did not contain an allowed value of argument 'field'. Allowed values are either: 'avatar_url', 'displayname'.")
|
||||
}
|
||||
} else {
|
||||
res = common.ProfileResponse{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
DisplayName: profile.DisplayName,
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: code,
|
||||
JSON: res,
|
||||
}
|
||||
}
|
||||
|
|
@ -106,13 +106,6 @@ func Setup(
|
|||
},
|
||||
)).Methods("GET")
|
||||
|
||||
v1fedmux.Handle("/version", common.MakeExternalAPI(
|
||||
"federation_version",
|
||||
func(httpReq *http.Request) util.JSONResponse {
|
||||
return Version()
|
||||
},
|
||||
)).Methods("GET")
|
||||
|
||||
v1fedmux.Handle("/query/directory/", common.MakeFedAPI(
|
||||
"federation_query_room_alias", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
|
|
@ -121,4 +114,29 @@ func Setup(
|
|||
)
|
||||
},
|
||||
)).Methods("Get")
|
||||
v1fedmux.Handle("/query/directory/?room_alias={alias}", common.MakeFedAPI(
|
||||
"federation_query_room_alias", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
vars := mux.Vars(httpReq)
|
||||
return RoomAliasToID(
|
||||
httpReq, federation, cfg, aliasAPI, vars["alias"],
|
||||
)
|
||||
},
|
||||
)).Methods("Get")
|
||||
|
||||
v1fedmux.Handle("/query/profile", common.MakeFedAPI(
|
||||
"federation_query_profile", cfg.Matrix.ServerName, keys,
|
||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||
return GetProfile(
|
||||
httpReq, accountDB, cfg,
|
||||
)
|
||||
},
|
||||
)).Methods("GET")
|
||||
|
||||
v1fedmux.Handle("/version", common.MakeExternalAPI(
|
||||
"federation_version",
|
||||
func(httpReq *http.Request) util.JSONResponse {
|
||||
return Version()
|
||||
},
|
||||
)).Methods("GET")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,11 +170,7 @@ func (t *txnReq) processEvent(e gomatrixserverlib.Event) error {
|
|||
// TODO: Check that the event is allowed by its auth_events.
|
||||
|
||||
// pass the event to the roomserver
|
||||
if err := t.producer.SendEvents(t.context, []gomatrixserverlib.Event{e}, api.DoNotSendToOtherServers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return t.producer.SendEvents(t.context, []gomatrixserverlib.Event{e}, api.DoNotSendToOtherServers)
|
||||
}
|
||||
|
||||
func checkAllowedByState(e gomatrixserverlib.Event, stateEvents []gomatrixserverlib.Event) error {
|
||||
|
|
@ -218,8 +214,5 @@ func (t *txnReq) processEventWithMissingState(e gomatrixserverlib.Event) error {
|
|||
return err
|
||||
}
|
||||
// pass the event along with the state to the roomserver
|
||||
if err := t.producer.SendEventWithState(t.context, state, e); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return t.producer.SendEventWithState(t.context, state, e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ func buildMembershipEvent(
|
|||
// them responded with an error.
|
||||
func sendToRemoteServer(
|
||||
ctx context.Context, inv invite,
|
||||
federation *gomatrixserverlib.FederationClient, cfg config.Dendrite,
|
||||
federation *gomatrixserverlib.FederationClient, _ config.Dendrite,
|
||||
builder gomatrixserverlib.EventBuilder,
|
||||
) (err error) {
|
||||
remoteServers := make([]gomatrixserverlib.ServerName, 2)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/federationsender/queue"
|
||||
|
|
@ -27,6 +26,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/federationsender/types"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
sarama "gopkg.in/Shopify/sarama.v1"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// destinationQueue is a queue of events for a single destination.
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ import (
|
|||
"fmt"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// OutgoingQueues is a collection of queues for sending transactions to other
|
||||
|
|
|
|||
|
|
@ -54,11 +54,7 @@ func (d *Database) prepare() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err = d.PartitionOffsetStatements.Prepare(d.db, "federationsender"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return d.PartitionOffsetStatements.Prepare(d.db, "federationsender")
|
||||
}
|
||||
|
||||
// UpdateRoom updates the joined hosts for a room and returns what the joined
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetPathFromBase64Hash evaluates the path to a media file from its Base64Hash
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/mediaapi/fileutils"
|
||||
|
|
@ -38,6 +37,7 @@ import (
|
|||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const mediaIDCharacters = "A-Za-z0-9_=-"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"net/url"
|
||||
"path"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/mediaapi/fileutils"
|
||||
|
|
@ -31,6 +30,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// uploadRequest metadata included in or derivable from an upload request
|
||||
|
|
@ -161,14 +161,10 @@ func (r *uploadRequest) doUpload(
|
|||
}
|
||||
}
|
||||
|
||||
if resErr := r.storeFileAndMetadata(
|
||||
return r.storeFileAndMetadata(
|
||||
ctx, tmpDir, cfg.Media.AbsBasePath, db, cfg.Media.ThumbnailSizes,
|
||||
activeThumbnailGeneration, cfg.Media.MaxThumbnailGenerators,
|
||||
); resErr != nil {
|
||||
return resErr
|
||||
}
|
||||
|
||||
return nil
|
||||
)
|
||||
}
|
||||
|
||||
// Validate validates the uploadRequest fields
|
||||
|
|
|
|||
|
|
@ -23,15 +23,13 @@ type statements struct {
|
|||
thumbnail thumbnailStatements
|
||||
}
|
||||
|
||||
func (s *statements) prepare(db *sql.DB) error {
|
||||
var err error
|
||||
|
||||
func (s *statements) prepare(db *sql.DB) (err error) {
|
||||
if err = s.media.prepare(db); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
if err = s.thumbnail.prepare(db); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ import (
|
|||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/mediaapi/storage"
|
||||
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type thumbnailFitness struct {
|
||||
|
|
@ -89,7 +89,7 @@ func SelectThumbnail(desired types.ThumbnailSize, thumbnails []*types.ThumbnailM
|
|||
}
|
||||
|
||||
// getActiveThumbnailGeneration checks for active thumbnail generation
|
||||
func getActiveThumbnailGeneration(dst types.Path, config types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, logger *log.Entry) (isActive bool, busy bool, errorReturn error) {
|
||||
func getActiveThumbnailGeneration(dst types.Path, _ types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, logger *log.Entry) (isActive bool, busy bool, errorReturn error) {
|
||||
// Check if there is active thumbnail generation.
|
||||
activeThumbnailGeneration.Lock()
|
||||
defer activeThumbnailGeneration.Unlock()
|
||||
|
|
@ -119,7 +119,7 @@ func getActiveThumbnailGeneration(dst types.Path, config types.ThumbnailSize, ac
|
|||
|
||||
// broadcastGeneration broadcasts that thumbnail generation completed and the error to all waiting goroutines
|
||||
// Note: This should only be called by the owner of the activeThumbnailGenerationResult
|
||||
func broadcastGeneration(dst types.Path, activeThumbnailGeneration *types.ActiveThumbnailGeneration, config types.ThumbnailSize, errorReturn error, logger *log.Entry) {
|
||||
func broadcastGeneration(dst types.Path, activeThumbnailGeneration *types.ActiveThumbnailGeneration, _ types.ThumbnailSize, errorReturn error, logger *log.Entry) {
|
||||
activeThumbnailGeneration.Lock()
|
||||
defer activeThumbnailGeneration.Unlock()
|
||||
if activeThumbnailGenerationResult, ok := activeThumbnailGeneration.PathToResult[string(dst)]; ok {
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/mediaapi/storage"
|
||||
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/h2non/bimg.v1"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/mediaapi/storage"
|
||||
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||
"github.com/nfnt/resize"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GenerateThumbnails generates the configured thumbnail sizes for the source file
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/publicroomsapi/storage"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
log "github.com/sirupsen/logrus"
|
||||
sarama "gopkg.in/Shopify/sarama.v1"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -79,11 +79,7 @@ func (r *RoomserverAliasAPI) SetRoomAlias(
|
|||
// At this point we've already committed the alias to the database so we
|
||||
// shouldn't cancel this request.
|
||||
// TODO: Ensure that we send unsent events when if server restarts.
|
||||
if err := r.sendUpdatedAliasesEvent(context.TODO(), request.UserID, request.RoomID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return r.sendUpdatedAliasesEvent(context.TODO(), request.UserID, request.RoomID)
|
||||
}
|
||||
|
||||
// GetAliasRoomID implements api.RoomserverAliasAPI
|
||||
|
|
@ -123,11 +119,7 @@ func (r *RoomserverAliasAPI) RemoveRoomAlias(
|
|||
// At this point we've already committed the alias to the database so we
|
||||
// shouldn't cancel this request.
|
||||
// TODO: Ensure that we send unsent events when if server restarts.
|
||||
if err := r.sendUpdatedAliasesEvent(context.TODO(), request.UserID, roomID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return r.sendUpdatedAliasesEvent(context.TODO(), request.UserID, roomID)
|
||||
}
|
||||
|
||||
type roomAliasesContent struct {
|
||||
|
|
|
|||
|
|
@ -129,11 +129,7 @@ func processRoomEvent(
|
|||
}
|
||||
|
||||
// Update the extremities of the event graph for the room
|
||||
if err := updateLatestEvents(ctx, db, ow, roomNID, stateAtEvent, event, input.SendAsServer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return updateLatestEvents(ctx, db, ow, roomNID, stateAtEvent, event, input.SendAsServer)
|
||||
}
|
||||
|
||||
func processInviteEvent(
|
||||
|
|
|
|||
|
|
@ -162,11 +162,7 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err = u.updater.MarkEventAsSent(u.stateAtEvent.EventNID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return u.updater.MarkEventAsSent(u.stateAtEvent.EventNID)
|
||||
}
|
||||
|
||||
func (u *latestEventsUpdater) latestState() error {
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/sync"
|
||||
log "github.com/sirupsen/logrus"
|
||||
sarama "gopkg.in/Shopify/sarama.v1"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/common/config"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
|
|
@ -27,6 +26,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/syncapi/sync"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
sarama "gopkg.in/Shopify/sarama.v1"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/sync"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
|
@ -28,7 +29,7 @@ import (
|
|||
const pathPrefixR0 = "/_matrix/client/r0"
|
||||
|
||||
// Setup configures the given mux with sync-server listeners
|
||||
func Setup(apiMux *mux.Router, srp *sync.RequestPool, deviceDB *devices.Database) {
|
||||
func Setup(apiMux *mux.Router, srp *sync.RequestPool, syncDB *storage.SyncServerDatabase, deviceDB *devices.Database) {
|
||||
r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
||||
|
||||
r0mux.Handle("/sync", common.MakeAuthAPI("sync", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||
|
|
@ -37,16 +38,16 @@ func Setup(apiMux *mux.Router, srp *sync.RequestPool, deviceDB *devices.Database
|
|||
|
||||
r0mux.Handle("/rooms/{roomID}/state", common.MakeAuthAPI("room_state", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||
vars := mux.Vars(req)
|
||||
return srp.OnIncomingStateRequest(req, vars["roomID"])
|
||||
return OnIncomingStateRequest(req, syncDB, vars["roomID"])
|
||||
})).Methods("GET")
|
||||
|
||||
r0mux.Handle("/rooms/{roomID}/state/{type}", common.MakeAuthAPI("room_state", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||
vars := mux.Vars(req)
|
||||
return srp.OnIncomingStateTypeRequest(req, vars["roomID"], vars["type"], "")
|
||||
return OnIncomingStateTypeRequest(req, syncDB, vars["roomID"], vars["type"], "")
|
||||
})).Methods("GET")
|
||||
|
||||
r0mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", common.MakeAuthAPI("room_state", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||
vars := mux.Vars(req)
|
||||
return srp.OnIncomingStateTypeRequest(req, vars["roomID"], vars["type"], vars["stateKey"])
|
||||
return OnIncomingStateTypeRequest(req, syncDB, vars["roomID"], vars["type"], vars["stateKey"])
|
||||
})).Methods("GET")
|
||||
}
|
||||
|
|
|
|||
118
src/github.com/matrix-org/dendrite/syncapi/routing/state.go
Normal file
118
src/github.com/matrix-org/dendrite/syncapi/routing/state.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright 2017 Vector Creations Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type stateEventInStateResp struct {
|
||||
gomatrixserverlib.ClientEvent
|
||||
PrevContent json.RawMessage `json:"prev_content,omitempty"`
|
||||
ReplacesState string `json:"replaces_state,omitempty"`
|
||||
}
|
||||
|
||||
// OnIncomingStateRequest is called when a client makes a /rooms/{roomID}/state
|
||||
// request. It will fetch all the state events from the specified room and will
|
||||
// append the necessary keys to them if applicable before returning them.
|
||||
// Returns an error if something went wrong in the process.
|
||||
// TODO: Check if the user is in the room. If not, check if the room's history
|
||||
// is publicly visible. Current behaviour is returning an empty array if the
|
||||
// user cannot see the room's history.
|
||||
func OnIncomingStateRequest(req *http.Request, db *storage.SyncServerDatabase, roomID string) util.JSONResponse {
|
||||
// TODO(#287): Auth request and handle the case where the user has left (where
|
||||
// we should return the state at the poin they left)
|
||||
|
||||
stateEvents, err := db.GetStateEventsForRoom(req.Context(), roomID)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
resp := []stateEventInStateResp{}
|
||||
// Fill the prev_content and replaces_state keys if necessary
|
||||
for _, event := range stateEvents {
|
||||
stateEvent := stateEventInStateResp{
|
||||
ClientEvent: gomatrixserverlib.ToClientEvent(event, gomatrixserverlib.FormatAll),
|
||||
}
|
||||
var prevEventRef types.PrevEventRef
|
||||
if len(event.Unsigned()) > 0 {
|
||||
if err := json.Unmarshal(event.Unsigned(), &prevEventRef); err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
// Fills the previous state event ID if the state event replaces another
|
||||
// state event
|
||||
if len(prevEventRef.ReplacesState) > 0 {
|
||||
stateEvent.ReplacesState = prevEventRef.ReplacesState
|
||||
}
|
||||
// Fill the previous event if the state event references a previous event
|
||||
if prevEventRef.PrevContent != nil {
|
||||
stateEvent.PrevContent = prevEventRef.PrevContent
|
||||
}
|
||||
}
|
||||
|
||||
resp = append(resp, stateEvent)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: resp,
|
||||
}
|
||||
}
|
||||
|
||||
// OnIncomingStateTypeRequest is called when a client makes a
|
||||
// /rooms/{roomID}/state/{type}/{statekey} request. It will look in current
|
||||
// state to see if there is an event with that type and state key, if there
|
||||
// is then (by default) we return the content, otherwise a 404.
|
||||
func OnIncomingStateTypeRequest(req *http.Request, db *storage.SyncServerDatabase, roomID string, evType, stateKey string) util.JSONResponse {
|
||||
// TODO(#287): Auth request and handle the case where the user has left (where
|
||||
// we should return the state at the poin they left)
|
||||
|
||||
logger := util.GetLogger(req.Context())
|
||||
logger.WithFields(log.Fields{
|
||||
"roomID": roomID,
|
||||
"evType": evType,
|
||||
"stateKey": stateKey,
|
||||
}).Info("Fetching state")
|
||||
|
||||
event, err := db.GetStateEvent(req.Context(), roomID, evType, stateKey)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
if event == nil {
|
||||
return util.JSONResponse{
|
||||
Code: 404,
|
||||
JSON: jsonerror.NotFound("cannot find state"),
|
||||
}
|
||||
}
|
||||
|
||||
stateEvent := stateEventInStateResp{
|
||||
ClientEvent: gomatrixserverlib.ToClientEvent(*event, gomatrixserverlib.FormatAll),
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: stateEvent.Content,
|
||||
}
|
||||
}
|
||||
|
|
@ -18,11 +18,11 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/lib/pq"
|
||||
"github.com/matrix-org/dendrite/common"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const outputRoomEventsSchema = `
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
"github.com/matrix-org/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const defaultSyncTimeout = time.Duration(30) * time.Second
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
|
@ -120,96 +119,6 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *authtype
|
|||
}
|
||||
}
|
||||
|
||||
type stateEventInStateResp struct {
|
||||
gomatrixserverlib.ClientEvent
|
||||
PrevContent json.RawMessage `json:"prev_content,omitempty"`
|
||||
ReplacesState string `json:"replaces_state,omitempty"`
|
||||
}
|
||||
|
||||
// OnIncomingStateRequest is called when a client makes a /rooms/{roomID}/state
|
||||
// request. It will fetch all the state events from the specified room and will
|
||||
// append the necessary keys to them if applicable before returning them.
|
||||
// Returns an error if something went wrong in the process.
|
||||
// TODO: Check if the user is in the room. If not, check if the room's history
|
||||
// is publicly visible. Current behaviour is returning an empty array if the
|
||||
// user cannot see the room's history.
|
||||
func (rp *RequestPool) OnIncomingStateRequest(req *http.Request, roomID string) util.JSONResponse {
|
||||
// TODO(#287): Auth request and handle the case where the user has left (where
|
||||
// we should return the state at the poin they left)
|
||||
|
||||
stateEvents, err := rp.db.GetStateEventsForRoom(req.Context(), roomID)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
resp := []stateEventInStateResp{}
|
||||
// Fill the prev_content and replaces_state keys if necessary
|
||||
for _, event := range stateEvents {
|
||||
stateEvent := stateEventInStateResp{
|
||||
ClientEvent: gomatrixserverlib.ToClientEvent(event, gomatrixserverlib.FormatAll),
|
||||
}
|
||||
var prevEventRef types.PrevEventRef
|
||||
if len(event.Unsigned()) > 0 {
|
||||
if err := json.Unmarshal(event.Unsigned(), &prevEventRef); err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
// Fills the previous state event ID if the state event replaces another
|
||||
// state event
|
||||
if len(prevEventRef.ReplacesState) > 0 {
|
||||
stateEvent.ReplacesState = prevEventRef.ReplacesState
|
||||
}
|
||||
// Fill the previous event if the state event references a previous event
|
||||
if prevEventRef.PrevContent != nil {
|
||||
stateEvent.PrevContent = prevEventRef.PrevContent
|
||||
}
|
||||
}
|
||||
|
||||
resp = append(resp, stateEvent)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: resp,
|
||||
}
|
||||
}
|
||||
|
||||
// OnIncomingStateTypeRequest is called when a client makes a
|
||||
// /rooms/{roomID}/state/{type}/{statekey} request. It will look in current
|
||||
// state to see if there is an event with that type and state key, if there
|
||||
// is then (by default) we return the content, otherwise a 404.
|
||||
func (rp *RequestPool) OnIncomingStateTypeRequest(req *http.Request, roomID string, evType, stateKey string) util.JSONResponse {
|
||||
// TODO(#287): Auth request and handle the case where the user has left (where
|
||||
// we should return the state at the poin they left)
|
||||
|
||||
logger := util.GetLogger(req.Context())
|
||||
logger.WithFields(log.Fields{
|
||||
"roomID": roomID,
|
||||
"evType": evType,
|
||||
"stateKey": stateKey,
|
||||
}).Info("Fetching state")
|
||||
|
||||
event, err := rp.db.GetStateEvent(req.Context(), roomID, evType, stateKey)
|
||||
if err != nil {
|
||||
return httputil.LogThenError(req, err)
|
||||
}
|
||||
|
||||
if event == nil {
|
||||
return util.JSONResponse{
|
||||
Code: 404,
|
||||
JSON: jsonerror.NotFound("cannot find state"),
|
||||
}
|
||||
}
|
||||
|
||||
stateEvent := stateEventInStateResp{
|
||||
ClientEvent: gomatrixserverlib.ToClientEvent(*event, gomatrixserverlib.FormatAll),
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: stateEvent.Content,
|
||||
}
|
||||
}
|
||||
|
||||
func (rp *RequestPool) currentSyncForUser(req syncRequest, currentPos types.StreamPosition) (res *types.Response, err error) {
|
||||
// TODO: handle ignored users
|
||||
if req.since == types.StreamPosition(0) {
|
||||
|
|
|
|||
6
vendor/manifest
vendored
6
vendor/manifest
vendored
|
|
@ -10,7 +10,7 @@
|
|||
{
|
||||
"importpath": "github.com/alecthomas/gometalinter",
|
||||
"repository": "https://github.com/alecthomas/gometalinter",
|
||||
"revision": "5507b26af3204e949ffe50ec08ee73e5847938e1",
|
||||
"revision": "0262fb20957a4c2d3bb7c834a6a125ae3884a2c6",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
|
|
@ -135,13 +135,13 @@
|
|||
{
|
||||
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
||||
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
||||
"revision": "fb17c27f65a0699b0d15f5311a530225b4aea5e0",
|
||||
"revision": "076933f95312aae3a9476e78d6b4118e1b45d542",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/matrix-org/naffka",
|
||||
"repository": "https://github.com/matrix-org/naffka",
|
||||
"revision": "d28656e34f96a8eeaab53e3b7678c9ce14af5786",
|
||||
"revision": "662bfd0841d0194bfe0a700d54226bb96eac574d",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
- [2. Analyse the debug output](#2-analyse-the-debug-output)
|
||||
- [3. Report an issue.](#3-report-an-issue)
|
||||
- [How do I filter issues between two git refs?](#how-do-i-filter-issues-between-two-git-refs)
|
||||
- [Details](#details)
|
||||
- [Checkstyle XML format](#checkstyle-xml-format)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
|
|
@ -57,12 +56,13 @@ It is intended for use with editor/IDE integration.
|
|||
- [go vet](https://golang.org/cmd/vet/) - Reports potential errors that otherwise compile.
|
||||
- [go tool vet --shadow](https://golang.org/cmd/vet/#hdr-Shadowed_variables) - Reports variables that may have been unintentionally shadowed.
|
||||
- [gotype](https://golang.org/x/tools/cmd/gotype) - Syntactic and semantic analysis similar to the Go compiler.
|
||||
- [gotype -x](https://golang.org/x/tools/cmd/gotype) - Syntactic and semantic analysis in external test packages (similar to the Go compiler).
|
||||
- [deadcode](https://github.com/tsenart/deadcode) - Finds unused code.
|
||||
- [gocyclo](https://github.com/alecthomas/gocyclo) - Computes the cyclomatic complexity of functions.
|
||||
- [golint](https://github.com/golang/lint) - Google's (mostly stylistic) linter.
|
||||
- [varcheck](https://github.com/opennota/check) - Find unused global variables and constants.
|
||||
- [structcheck](https://github.com/opennota/check) - Find unused struct fields.
|
||||
- [aligncheck](https://github.com/opennota/check) - Warn about un-optimally aligned structures.
|
||||
- [maligned](https://github.com/mdempsky/maligned) - Detect structs that would take less memory if their fields were sorted.
|
||||
- [errcheck](https://github.com/kisielk/errcheck) - Check that error return values are used.
|
||||
- [megacheck](https://github.com/dominikh/go-tools/tree/master/cmd/megacheck) - Run staticcheck, gosimple and unused, sharing work.
|
||||
- [dupl](https://github.com/mibk/dupl) - Reports potentially duplicated code.
|
||||
|
|
@ -81,6 +81,7 @@ Disabled by default (enable with `--enable=<linter>`):
|
|||
- [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple) - Report simplifications in code.
|
||||
- [lll](https://github.com/walle/lll) - Report long lines (see `--line-length=N`).
|
||||
- [misspell](https://github.com/client9/misspell) - Finds commonly misspelled English words.
|
||||
- [nakedret](https://github.com/alexkohler/nakedret) - Finds naked returns.
|
||||
- [unparam](https://github.com/mvdan/unparam) - Find unused function parameters.
|
||||
- [unused](https://github.com/dominikh/go-tools/tree/master/cmd/unused) - Find unused variables.
|
||||
- [safesql](https://github.com/stripe/safesql) - Finds potential SQL injection vulnerabilities.
|
||||
|
|
@ -91,14 +92,15 @@ Additional linters can be added through the command line with `--linter=NAME:COM
|
|||
## Configuration file
|
||||
|
||||
gometalinter now supports a JSON configuration file which can be loaded via
|
||||
`--config=<file>`. The format of this file is determined by the Config struct
|
||||
in `config.go`.
|
||||
`--config=<file>`. The format of this file is determined by the `Config` struct
|
||||
in [config.go](https://github.com/alecthomas/gometalinter/blob/master/config.go).
|
||||
|
||||
The configuration file mostly corresponds to command-line flags, with the following exceptions:
|
||||
|
||||
- Linters defined in the configuration file will overlay existing definitions, not replace them.
|
||||
- "Enable" defines the exact set of linters that will be enabled (default
|
||||
linters are disabled).
|
||||
linters are disabled). `--help` displays the list of default linters with the exact names
|
||||
you must use.
|
||||
|
||||
Here is an example configuration file:
|
||||
|
||||
|
|
@ -108,6 +110,34 @@ Here is an example configuration file:
|
|||
}
|
||||
```
|
||||
|
||||
### Adding Custom linters
|
||||
|
||||
Linters can be added and customized from the config file using the `Linters` field.
|
||||
Linters supports the following fields:
|
||||
|
||||
* `Command` - the path to the linter binary and any default arguments
|
||||
* `Pattern` - a regular expression used to parse the linter output
|
||||
* `IsFast` - if the linter should be run when the `--fast` flag is used
|
||||
* `PartitionStrategy` - how paths args should be passed to the linter command:
|
||||
* `directories` - call the linter once with a list of all the directories
|
||||
* `files` - call the linter once with a list of all the files
|
||||
* `packages` - call the linter once with a list of all the package paths
|
||||
* `files-by-package` - call the linter once per package with a list of the
|
||||
files in the package.
|
||||
* `single-directory` - call the linter once per directory
|
||||
|
||||
The config for default linters can be overridden by using the name of the
|
||||
linter.
|
||||
|
||||
Additional linters can be configured via the command line using the format
|
||||
`NAME:COMMAND:PATTERN`.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
$ gometalinter --linter='vet:go tool vet -printfuncs=Infof,Debugf,Warningf,Errorf:PATH:LINE:MESSAGE' .
|
||||
```
|
||||
|
||||
## Installing
|
||||
|
||||
There are two options for installing gometalinter.
|
||||
|
|
@ -171,7 +201,8 @@ Install all known linters:
|
|||
$ gometalinter --install
|
||||
Installing:
|
||||
structcheck
|
||||
aligncheck
|
||||
maligned
|
||||
nakedret
|
||||
deadcode
|
||||
gocyclo
|
||||
ineffassign
|
||||
|
|
@ -308,21 +339,6 @@ gometalinter |& revgrep master # Show issues between master and HEAD (or
|
|||
gometalinter |& revgrep origin/master # Show issues that haven't been pushed.
|
||||
```
|
||||
|
||||
## Details
|
||||
|
||||
Additional linters can be configured via the command line:
|
||||
|
||||
```
|
||||
$ gometalinter --linter='vet:go tool vet -printfuncs=Infof,Debugf,Warningf,Errorf:PATH:LINE:MESSAGE' .
|
||||
stutter.go:21:15:warning: error return value not checked (defer a.Close()) (errcheck)
|
||||
stutter.go:22:15:warning: error return value not checked (defer a.Close()) (errcheck)
|
||||
stutter.go:27:6:warning: error return value not checked (doit() // test for errcheck) (errcheck)
|
||||
stutter.go:9::warning: unused global variable unusedGlobal (varcheck)
|
||||
stutter.go:13::warning: unused struct field MyStruct.Unused (structcheck)
|
||||
stutter.go:12:6:warning: exported type MyStruct should have comment or be unexported (golint)
|
||||
stutter.go:16:6:warning: exported type PublicUndocumented should have comment or be unexported (deadcode)
|
||||
```
|
||||
|
||||
## Checkstyle XML format
|
||||
|
||||
`gometalinter` supports [checkstyle](http://checkstyle.sourceforge.net/)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Alex Kohler
|
||||
|
||||
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.
|
||||
310
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/alexkohler/nakedret/import.go
vendored
Normal file
310
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/alexkohler/nakedret/import.go
vendored
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
package main
|
||||
|
||||
/*
|
||||
|
||||
This file holds a direct copy of the import path matching code of
|
||||
https://github.com/golang/go/blob/master/src/cmd/go/main.go. It can be
|
||||
replaced when https://golang.org/issue/8768 is resolved.
|
||||
|
||||
It has been updated to follow upstream changes in a few ways.
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var buildContext = build.Default
|
||||
|
||||
var (
|
||||
goroot = filepath.Clean(runtime.GOROOT())
|
||||
gorootSrc = filepath.Join(goroot, "src")
|
||||
)
|
||||
|
||||
// importPathsNoDotExpansion returns the import paths to use for the given
|
||||
// command line, but it does no ... expansion.
|
||||
func importPathsNoDotExpansion(args []string) []string {
|
||||
if len(args) == 0 {
|
||||
return []string{"."}
|
||||
}
|
||||
var out []string
|
||||
for _, a := range args {
|
||||
// Arguments are supposed to be import paths, but
|
||||
// as a courtesy to Windows developers, rewrite \ to /
|
||||
// in command-line arguments. Handles .\... and so on.
|
||||
if filepath.Separator == '\\' {
|
||||
a = strings.Replace(a, `\`, `/`, -1)
|
||||
}
|
||||
|
||||
// Put argument in canonical form, but preserve leading ./.
|
||||
if strings.HasPrefix(a, "./") {
|
||||
a = "./" + path.Clean(a)
|
||||
if a == "./." {
|
||||
a = "."
|
||||
}
|
||||
} else {
|
||||
a = path.Clean(a)
|
||||
}
|
||||
if a == "all" || a == "std" {
|
||||
out = append(out, allPackages(a)...)
|
||||
continue
|
||||
}
|
||||
out = append(out, a)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// importPaths returns the import paths to use for the given command line.
|
||||
func importPaths(args []string) []string {
|
||||
args = importPathsNoDotExpansion(args)
|
||||
var out []string
|
||||
for _, a := range args {
|
||||
if strings.Contains(a, "...") {
|
||||
if build.IsLocalImport(a) {
|
||||
out = append(out, allPackagesInFS(a)...)
|
||||
} else {
|
||||
out = append(out, allPackages(a)...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
out = append(out, a)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// matchPattern(pattern)(name) reports whether
|
||||
// name matches pattern. Pattern is a limited glob
|
||||
// pattern in which '...' means 'any string' and there
|
||||
// is no other special syntax.
|
||||
func matchPattern(pattern string) func(name string) bool {
|
||||
re := regexp.QuoteMeta(pattern)
|
||||
re = strings.Replace(re, `\.\.\.`, `.*`, -1)
|
||||
// Special case: foo/... matches foo too.
|
||||
if strings.HasSuffix(re, `/.*`) {
|
||||
re = re[:len(re)-len(`/.*`)] + `(/.*)?`
|
||||
}
|
||||
reg := regexp.MustCompile(`^` + re + `$`)
|
||||
return func(name string) bool {
|
||||
return reg.MatchString(name)
|
||||
}
|
||||
}
|
||||
|
||||
// hasPathPrefix reports whether the path s begins with the
|
||||
// elements in prefix.
|
||||
func hasPathPrefix(s, prefix string) bool {
|
||||
switch {
|
||||
default:
|
||||
return false
|
||||
case len(s) == len(prefix):
|
||||
return s == prefix
|
||||
case len(s) > len(prefix):
|
||||
if prefix != "" && prefix[len(prefix)-1] == '/' {
|
||||
return strings.HasPrefix(s, prefix)
|
||||
}
|
||||
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
|
||||
}
|
||||
}
|
||||
|
||||
// treeCanMatchPattern(pattern)(name) reports whether
|
||||
// name or children of name can possibly match pattern.
|
||||
// Pattern is the same limited glob accepted by matchPattern.
|
||||
func treeCanMatchPattern(pattern string) func(name string) bool {
|
||||
wildCard := false
|
||||
if i := strings.Index(pattern, "..."); i >= 0 {
|
||||
wildCard = true
|
||||
pattern = pattern[:i]
|
||||
}
|
||||
return func(name string) bool {
|
||||
return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
|
||||
wildCard && strings.HasPrefix(name, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// allPackages returns all the packages that can be found
|
||||
// under the $GOPATH directories and $GOROOT matching pattern.
|
||||
// The pattern is either "all" (all packages), "std" (standard packages)
|
||||
// or a path including "...".
|
||||
func allPackages(pattern string) []string {
|
||||
pkgs := matchPackages(pattern)
|
||||
if len(pkgs) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func matchPackages(pattern string) []string {
|
||||
match := func(string) bool { return true }
|
||||
treeCanMatch := func(string) bool { return true }
|
||||
if pattern != "all" && pattern != "std" {
|
||||
match = matchPattern(pattern)
|
||||
treeCanMatch = treeCanMatchPattern(pattern)
|
||||
}
|
||||
|
||||
have := map[string]bool{
|
||||
"builtin": true, // ignore pseudo-package that exists only for documentation
|
||||
}
|
||||
if !buildContext.CgoEnabled {
|
||||
have["runtime/cgo"] = true // ignore during walk
|
||||
}
|
||||
var pkgs []string
|
||||
|
||||
// Commands
|
||||
cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
|
||||
filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil || !fi.IsDir() || path == cmd {
|
||||
return nil
|
||||
}
|
||||
name := path[len(cmd):]
|
||||
if !treeCanMatch(name) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
// Commands are all in cmd/, not in subdirectories.
|
||||
if strings.Contains(name, string(filepath.Separator)) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
// We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
|
||||
name = "cmd/" + name
|
||||
if have[name] {
|
||||
return nil
|
||||
}
|
||||
have[name] = true
|
||||
if !match(name) {
|
||||
return nil
|
||||
}
|
||||
_, err = buildContext.ImportDir(path, 0)
|
||||
if err != nil {
|
||||
if _, noGo := err.(*build.NoGoError); !noGo {
|
||||
log.Print(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
pkgs = append(pkgs, name)
|
||||
return nil
|
||||
})
|
||||
|
||||
for _, src := range buildContext.SrcDirs() {
|
||||
if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
|
||||
continue
|
||||
}
|
||||
src = filepath.Clean(src) + string(filepath.Separator)
|
||||
root := src
|
||||
if pattern == "cmd" {
|
||||
root += "cmd" + string(filepath.Separator)
|
||||
}
|
||||
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil || !fi.IsDir() || path == src {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Avoid .foo, _foo, testdata and vendor directory trees.
|
||||
_, elem := filepath.Split(path)
|
||||
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" || elem == "vendor" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
name := filepath.ToSlash(path[len(src):])
|
||||
if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") {
|
||||
// The name "std" is only the standard library.
|
||||
// If the name is cmd, it's the root of the command tree.
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if !treeCanMatch(name) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if have[name] {
|
||||
return nil
|
||||
}
|
||||
have[name] = true
|
||||
if !match(name) {
|
||||
return nil
|
||||
}
|
||||
_, err = buildContext.ImportDir(path, 0)
|
||||
if err != nil {
|
||||
if _, noGo := err.(*build.NoGoError); noGo {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
pkgs = append(pkgs, name)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
// allPackagesInFS is like allPackages but is passed a pattern
|
||||
// beginning ./ or ../, meaning it should scan the tree rooted
|
||||
// at the given directory. There are ... in the pattern too.
|
||||
func allPackagesInFS(pattern string) []string {
|
||||
pkgs := matchPackagesInFS(pattern)
|
||||
if len(pkgs) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func matchPackagesInFS(pattern string) []string {
|
||||
// Find directory to begin the scan.
|
||||
// Could be smarter but this one optimization
|
||||
// is enough for now, since ... is usually at the
|
||||
// end of a path.
|
||||
i := strings.Index(pattern, "...")
|
||||
dir, _ := path.Split(pattern[:i])
|
||||
|
||||
// pattern begins with ./ or ../.
|
||||
// path.Clean will discard the ./ but not the ../.
|
||||
// We need to preserve the ./ for pattern matching
|
||||
// and in the returned import paths.
|
||||
prefix := ""
|
||||
if strings.HasPrefix(pattern, "./") {
|
||||
prefix = "./"
|
||||
}
|
||||
match := matchPattern(pattern)
|
||||
|
||||
var pkgs []string
|
||||
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil || !fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if path == dir {
|
||||
// filepath.Walk starts at dir and recurses. For the recursive case,
|
||||
// the path is the result of filepath.Join, which calls filepath.Clean.
|
||||
// The initial case is not Cleaned, though, so we do this explicitly.
|
||||
//
|
||||
// This converts a path like "./io/" to "io". Without this step, running
|
||||
// "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io
|
||||
// package, because prepending the prefix "./" to the unclean path would
|
||||
// result in "././io", and match("././io") returns false.
|
||||
path = filepath.Clean(path)
|
||||
}
|
||||
|
||||
// Avoid .foo, _foo, testdata and vendor directory trees, but do not avoid "." or "..".
|
||||
_, elem := filepath.Split(path)
|
||||
dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
|
||||
if dot || strings.HasPrefix(elem, "_") || elem == "testdata" || elem == "vendor" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
name := prefix + filepath.ToSlash(path)
|
||||
if !match(name) {
|
||||
return nil
|
||||
}
|
||||
if _, err = build.ImportDir(path, 0); err != nil {
|
||||
if _, noGo := err.(*build.NoGoError); !noGo {
|
||||
log.Print(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
pkgs = append(pkgs, name)
|
||||
return nil
|
||||
})
|
||||
return pkgs
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
pwd = "./"
|
||||
)
|
||||
|
||||
func init() {
|
||||
//TODO allow build tags
|
||||
build.Default.UseAllFiles = true
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Printf("Usage of %s:\n", os.Args[0])
|
||||
log.Printf("\nnakedret [flags] # runs on package in current directory\n")
|
||||
log.Printf("\nnakedret [flags] [packages]\n")
|
||||
log.Printf("Flags:\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
type returnsVisitor struct {
|
||||
f *token.FileSet
|
||||
maxLength uint
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// Remove log timestamp
|
||||
log.SetFlags(0)
|
||||
|
||||
maxLength := flag.Uint("l", 5, "maximum number of lines for a naked return function")
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if err := checkNakedReturns(flag.Args(), maxLength); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkNakedReturns(args []string, maxLength *uint) error {
|
||||
|
||||
fset := token.NewFileSet()
|
||||
|
||||
files, err := parseInput(args, fset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse input %v", err)
|
||||
}
|
||||
|
||||
if maxLength == nil {
|
||||
return errors.New("max length nil")
|
||||
}
|
||||
|
||||
retVis := &returnsVisitor{
|
||||
f: fset,
|
||||
maxLength: *maxLength,
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
ast.Walk(retVis, f)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseInput(args []string, fset *token.FileSet) ([]*ast.File, error) {
|
||||
var directoryList []string
|
||||
var fileMode bool
|
||||
files := make([]*ast.File, 0)
|
||||
|
||||
if len(args) == 0 {
|
||||
directoryList = append(directoryList, pwd)
|
||||
} else {
|
||||
for _, arg := range args {
|
||||
if strings.HasSuffix(arg, "/...") && isDir(arg[:len(arg)-len("/...")]) {
|
||||
|
||||
for _, dirname := range allPackagesInFS(arg) {
|
||||
directoryList = append(directoryList, dirname)
|
||||
}
|
||||
|
||||
} else if isDir(arg) {
|
||||
directoryList = append(directoryList, arg)
|
||||
|
||||
} else if exists(arg) {
|
||||
if strings.HasSuffix(arg, ".go") {
|
||||
fileMode = true
|
||||
f, err := parser.ParseFile(fset, arg, nil, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, f)
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid file %v specified", arg)
|
||||
}
|
||||
} else {
|
||||
|
||||
//TODO clean this up a bit
|
||||
imPaths := importPaths([]string{arg})
|
||||
for _, importPath := range imPaths {
|
||||
pkg, err := build.Import(importPath, ".", 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var stringFiles []string
|
||||
stringFiles = append(stringFiles, pkg.GoFiles...)
|
||||
// files = append(files, pkg.CgoFiles...)
|
||||
stringFiles = append(stringFiles, pkg.TestGoFiles...)
|
||||
if pkg.Dir != "." {
|
||||
for i, f := range stringFiles {
|
||||
stringFiles[i] = filepath.Join(pkg.Dir, f)
|
||||
}
|
||||
}
|
||||
|
||||
fileMode = true
|
||||
for _, stringFile := range stringFiles {
|
||||
f, err := parser.ParseFile(fset, stringFile, nil, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we're not in file mode, then we need to grab each and every package in each directory
|
||||
// we can to grab all the files
|
||||
if !fileMode {
|
||||
for _, fpath := range directoryList {
|
||||
pkgs, err := parser.ParseDir(fset, fpath, nil, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
for _, f := range pkg.Files {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func isDir(filename string) bool {
|
||||
fi, err := os.Stat(filename)
|
||||
return err == nil && fi.IsDir()
|
||||
}
|
||||
|
||||
func exists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor {
|
||||
var namedReturns []*ast.Ident
|
||||
|
||||
funcDecl, ok := node.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
return v
|
||||
}
|
||||
var functionLineLength int
|
||||
// We've found a function
|
||||
if funcDecl.Type != nil && funcDecl.Type.Results != nil {
|
||||
for _, field := range funcDecl.Type.Results.List {
|
||||
for _, ident := range field.Names {
|
||||
if ident != nil {
|
||||
namedReturns = append(namedReturns, ident)
|
||||
}
|
||||
}
|
||||
}
|
||||
file := v.f.File(funcDecl.Pos())
|
||||
functionLineLength = file.Position(funcDecl.End()).Line - file.Position(funcDecl.Pos()).Line
|
||||
}
|
||||
|
||||
if len(namedReturns) > 0 && funcDecl.Body != nil {
|
||||
// Scan the body for usage of the named returns
|
||||
for _, stmt := range funcDecl.Body.List {
|
||||
|
||||
switch s := stmt.(type) {
|
||||
case *ast.ReturnStmt:
|
||||
if len(s.Results) == 0 {
|
||||
file := v.f.File(s.Pos())
|
||||
if file != nil && uint(functionLineLength) > v.maxLength {
|
||||
if funcDecl.Name != nil {
|
||||
log.Printf("%v:%v %v naked returns on %v line function \n", file.Name(), file.Position(s.Pos()).Line, funcDecl.Name.Name, functionLineLength)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
27
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/LICENSE
vendored
Normal file
27
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/LICENSE
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.
|
||||
682
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/asmdecl.go
vendored
Normal file
682
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/asmdecl.go
vendored
Normal file
|
|
@ -0,0 +1,682 @@
|
|||
// 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.
|
||||
|
||||
// Identify mismatches between assembly files and Go func declarations.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 'kind' is a kind of assembly variable.
|
||||
// The kinds 1, 2, 4, 8 stand for values of that size.
|
||||
type asmKind int
|
||||
|
||||
// These special kinds are not valid sizes.
|
||||
const (
|
||||
asmString asmKind = 100 + iota
|
||||
asmSlice
|
||||
asmInterface
|
||||
asmEmptyInterface
|
||||
)
|
||||
|
||||
// An asmArch describes assembly parameters for an architecture
|
||||
type asmArch struct {
|
||||
name string
|
||||
ptrSize int
|
||||
intSize int
|
||||
maxAlign int
|
||||
bigEndian bool
|
||||
stack string
|
||||
lr bool
|
||||
}
|
||||
|
||||
// An asmFunc describes the expected variables for a function on a given architecture.
|
||||
type asmFunc struct {
|
||||
arch *asmArch
|
||||
size int // size of all arguments
|
||||
vars map[string]*asmVar
|
||||
varByOffset map[int]*asmVar
|
||||
}
|
||||
|
||||
// An asmVar describes a single assembly variable.
|
||||
type asmVar struct {
|
||||
name string
|
||||
kind asmKind
|
||||
typ string
|
||||
off int
|
||||
size int
|
||||
inner []*asmVar
|
||||
}
|
||||
|
||||
var (
|
||||
asmArch386 = asmArch{"386", 4, 4, 4, false, "SP", false}
|
||||
asmArchArm = asmArch{"arm", 4, 4, 4, false, "R13", true}
|
||||
asmArchArm64 = asmArch{"arm64", 8, 8, 8, false, "RSP", true}
|
||||
asmArchAmd64 = asmArch{"amd64", 8, 8, 8, false, "SP", false}
|
||||
asmArchAmd64p32 = asmArch{"amd64p32", 4, 4, 8, false, "SP", false}
|
||||
asmArchMips64 = asmArch{"mips64", 8, 8, 8, true, "R29", true}
|
||||
asmArchMips64LE = asmArch{"mips64", 8, 8, 8, false, "R29", true}
|
||||
asmArchPpc64 = asmArch{"ppc64", 8, 8, 8, true, "R1", true}
|
||||
asmArchPpc64LE = asmArch{"ppc64le", 8, 8, 8, false, "R1", true}
|
||||
|
||||
arches = []*asmArch{
|
||||
&asmArch386,
|
||||
&asmArchArm,
|
||||
&asmArchArm64,
|
||||
&asmArchAmd64,
|
||||
&asmArchAmd64p32,
|
||||
&asmArchMips64,
|
||||
&asmArchMips64LE,
|
||||
&asmArchPpc64,
|
||||
&asmArchPpc64LE,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
re = regexp.MustCompile
|
||||
asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
|
||||
asmTEXT = re(`\bTEXT\b.*·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
|
||||
asmDATA = re(`\b(DATA|GLOBL)\b`)
|
||||
asmNamedFP = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
|
||||
asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`)
|
||||
asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
|
||||
asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
|
||||
ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`)
|
||||
)
|
||||
|
||||
func asmCheck(pkg *Package) {
|
||||
if !vet("asmdecl") {
|
||||
return
|
||||
}
|
||||
|
||||
// No work if no assembly files.
|
||||
if !pkg.hasFileWithSuffix(".s") {
|
||||
return
|
||||
}
|
||||
|
||||
// Gather declarations. knownFunc[name][arch] is func description.
|
||||
knownFunc := make(map[string]map[string]*asmFunc)
|
||||
|
||||
for _, f := range pkg.files {
|
||||
if f.file != nil {
|
||||
for _, decl := range f.file.Decls {
|
||||
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil {
|
||||
knownFunc[decl.Name.Name] = f.asmParseDecl(decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Files:
|
||||
for _, f := range pkg.files {
|
||||
if !strings.HasSuffix(f.name, ".s") {
|
||||
continue
|
||||
}
|
||||
Println("Checking file", f.name)
|
||||
|
||||
// Determine architecture from file name if possible.
|
||||
var arch string
|
||||
var archDef *asmArch
|
||||
for _, a := range arches {
|
||||
if strings.HasSuffix(f.name, "_"+a.name+".s") {
|
||||
arch = a.name
|
||||
archDef = a
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
lines := strings.SplitAfter(string(f.content), "\n")
|
||||
var (
|
||||
fn *asmFunc
|
||||
fnName string
|
||||
localSize, argSize int
|
||||
wroteSP bool
|
||||
haveRetArg bool
|
||||
retLine []int
|
||||
)
|
||||
|
||||
flushRet := func() {
|
||||
if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
|
||||
v := fn.vars["ret"]
|
||||
for _, line := range retLine {
|
||||
f.Badf(token.NoPos, "%s:%d: [%s] %s: RET without writing to %d-byte ret+%d(FP)", f.name, line, arch, fnName, v.size, v.off)
|
||||
}
|
||||
}
|
||||
retLine = nil
|
||||
}
|
||||
for lineno, line := range lines {
|
||||
lineno++
|
||||
|
||||
badf := func(format string, args ...interface{}) {
|
||||
f.Badf(token.NoPos, "%s:%d: [%s] %s: %s", f.name, lineno, arch, fnName, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
if arch == "" {
|
||||
// Determine architecture from +build line if possible.
|
||||
if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
|
||||
Fields:
|
||||
for _, fld := range strings.Fields(m[1]) {
|
||||
for _, a := range arches {
|
||||
if a.name == fld {
|
||||
arch = a.name
|
||||
archDef = a
|
||||
break Fields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m := asmTEXT.FindStringSubmatch(line); m != nil {
|
||||
flushRet()
|
||||
if arch == "" {
|
||||
f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name)
|
||||
continue Files
|
||||
}
|
||||
fnName = m[1]
|
||||
fn = knownFunc[m[1]][arch]
|
||||
if fn != nil {
|
||||
size, _ := strconv.Atoi(m[4])
|
||||
if size != fn.size && (m[2] != "7" && !strings.Contains(m[2], "NOSPLIT") || size != 0) {
|
||||
badf("wrong argument size %d; expected $...-%d", size, fn.size)
|
||||
}
|
||||
}
|
||||
localSize, _ = strconv.Atoi(m[3])
|
||||
localSize += archDef.intSize
|
||||
if archDef.lr {
|
||||
// Account for caller's saved LR
|
||||
localSize += archDef.intSize
|
||||
}
|
||||
argSize, _ = strconv.Atoi(m[4])
|
||||
if fn == nil && !strings.Contains(fnName, "<>") {
|
||||
badf("function %s missing Go declaration", fnName)
|
||||
}
|
||||
wroteSP = false
|
||||
haveRetArg = false
|
||||
continue
|
||||
} else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") {
|
||||
// function, but not visible from Go (didn't match asmTEXT), so stop checking
|
||||
flushRet()
|
||||
fn = nil
|
||||
fnName = ""
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(line, "RET") {
|
||||
retLine = append(retLine, lineno)
|
||||
}
|
||||
|
||||
if fnName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if asmDATA.FindStringSubmatch(line) != nil {
|
||||
fn = nil
|
||||
}
|
||||
|
||||
if archDef == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) {
|
||||
wroteSP = true
|
||||
continue
|
||||
}
|
||||
|
||||
for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
|
||||
if m[3] != archDef.stack || wroteSP {
|
||||
continue
|
||||
}
|
||||
off := 0
|
||||
if m[1] != "" {
|
||||
off, _ = strconv.Atoi(m[2])
|
||||
}
|
||||
if off >= localSize {
|
||||
if fn != nil {
|
||||
v := fn.varByOffset[off-localSize]
|
||||
if v != nil {
|
||||
badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if off >= localSize+argSize {
|
||||
badf("use of %s points beyond argument frame", m[1])
|
||||
continue
|
||||
}
|
||||
badf("use of %s to access argument frame", m[1])
|
||||
}
|
||||
}
|
||||
|
||||
if fn == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) {
|
||||
off, _ := strconv.Atoi(m[2])
|
||||
v := fn.varByOffset[off]
|
||||
if v != nil {
|
||||
badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off)
|
||||
} else {
|
||||
badf("use of unnamed argument %s", m[1])
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
|
||||
name := m[1]
|
||||
off := 0
|
||||
if m[2] != "" {
|
||||
off, _ = strconv.Atoi(m[2])
|
||||
}
|
||||
if name == "ret" || strings.HasPrefix(name, "ret_") {
|
||||
haveRetArg = true
|
||||
}
|
||||
v := fn.vars[name]
|
||||
if v == nil {
|
||||
// Allow argframe+0(FP).
|
||||
if name == "argframe" && off == 0 {
|
||||
continue
|
||||
}
|
||||
v = fn.varByOffset[off]
|
||||
if v != nil {
|
||||
badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off)
|
||||
} else {
|
||||
badf("unknown variable %s", name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
asmCheckVar(badf, fn, line, m[0], off, v)
|
||||
}
|
||||
}
|
||||
flushRet()
|
||||
}
|
||||
}
|
||||
|
||||
// asmParseDecl parses a function decl for expected assembly variables.
|
||||
func (f *File) asmParseDecl(decl *ast.FuncDecl) map[string]*asmFunc {
|
||||
var (
|
||||
arch *asmArch
|
||||
fn *asmFunc
|
||||
offset int
|
||||
failed bool
|
||||
)
|
||||
|
||||
addVar := func(outer string, v asmVar) {
|
||||
if vo := fn.vars[outer]; vo != nil {
|
||||
vo.inner = append(vo.inner, &v)
|
||||
}
|
||||
fn.vars[v.name] = &v
|
||||
for i := 0; i < v.size; i++ {
|
||||
fn.varByOffset[v.off+i] = &v
|
||||
}
|
||||
}
|
||||
|
||||
addParams := func(list []*ast.Field) {
|
||||
for i, fld := range list {
|
||||
// Determine alignment, size, and kind of type in declaration.
|
||||
var align, size int
|
||||
var kind asmKind
|
||||
names := fld.Names
|
||||
typ := f.gofmt(fld.Type)
|
||||
switch t := fld.Type.(type) {
|
||||
default:
|
||||
switch typ {
|
||||
default:
|
||||
f.Warnf(fld.Type.Pos(), "unknown assembly argument type %s", typ)
|
||||
failed = true
|
||||
return
|
||||
case "int8", "uint8", "byte", "bool":
|
||||
size = 1
|
||||
case "int16", "uint16":
|
||||
size = 2
|
||||
case "int32", "uint32", "float32":
|
||||
size = 4
|
||||
case "int64", "uint64", "float64":
|
||||
align = arch.maxAlign
|
||||
size = 8
|
||||
case "int", "uint":
|
||||
size = arch.intSize
|
||||
case "uintptr", "iword", "Word", "Errno", "unsafe.Pointer":
|
||||
size = arch.ptrSize
|
||||
case "string", "ErrorString":
|
||||
size = arch.ptrSize * 2
|
||||
align = arch.ptrSize
|
||||
kind = asmString
|
||||
}
|
||||
case *ast.ChanType, *ast.FuncType, *ast.MapType, *ast.StarExpr:
|
||||
size = arch.ptrSize
|
||||
case *ast.InterfaceType:
|
||||
align = arch.ptrSize
|
||||
size = 2 * arch.ptrSize
|
||||
if len(t.Methods.List) > 0 {
|
||||
kind = asmInterface
|
||||
} else {
|
||||
kind = asmEmptyInterface
|
||||
}
|
||||
case *ast.ArrayType:
|
||||
if t.Len == nil {
|
||||
size = arch.ptrSize + 2*arch.intSize
|
||||
align = arch.ptrSize
|
||||
kind = asmSlice
|
||||
break
|
||||
}
|
||||
f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ)
|
||||
failed = true
|
||||
case *ast.StructType:
|
||||
f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ)
|
||||
failed = true
|
||||
}
|
||||
if align == 0 {
|
||||
align = size
|
||||
}
|
||||
if kind == 0 {
|
||||
kind = asmKind(size)
|
||||
}
|
||||
offset += -offset & (align - 1)
|
||||
|
||||
// Create variable for each name being declared with this type.
|
||||
if len(names) == 0 {
|
||||
name := "unnamed"
|
||||
if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 && &list[0] == &decl.Type.Results.List[0] && i == 0 {
|
||||
// Assume assembly will refer to single unnamed result as r.
|
||||
name = "ret"
|
||||
}
|
||||
names = []*ast.Ident{{Name: name}}
|
||||
}
|
||||
for _, id := range names {
|
||||
name := id.Name
|
||||
addVar("", asmVar{
|
||||
name: name,
|
||||
kind: kind,
|
||||
typ: typ,
|
||||
off: offset,
|
||||
size: size,
|
||||
})
|
||||
switch kind {
|
||||
case 8:
|
||||
if arch.ptrSize == 4 {
|
||||
w1, w2 := "lo", "hi"
|
||||
if arch.bigEndian {
|
||||
w1, w2 = w2, w1
|
||||
}
|
||||
addVar(name, asmVar{
|
||||
name: name + "_" + w1,
|
||||
kind: 4,
|
||||
typ: "half " + typ,
|
||||
off: offset,
|
||||
size: 4,
|
||||
})
|
||||
addVar(name, asmVar{
|
||||
name: name + "_" + w2,
|
||||
kind: 4,
|
||||
typ: "half " + typ,
|
||||
off: offset + 4,
|
||||
size: 4,
|
||||
})
|
||||
}
|
||||
|
||||
case asmEmptyInterface:
|
||||
addVar(name, asmVar{
|
||||
name: name + "_type",
|
||||
kind: asmKind(arch.ptrSize),
|
||||
typ: "interface type",
|
||||
off: offset,
|
||||
size: arch.ptrSize,
|
||||
})
|
||||
addVar(name, asmVar{
|
||||
name: name + "_data",
|
||||
kind: asmKind(arch.ptrSize),
|
||||
typ: "interface data",
|
||||
off: offset + arch.ptrSize,
|
||||
size: arch.ptrSize,
|
||||
})
|
||||
|
||||
case asmInterface:
|
||||
addVar(name, asmVar{
|
||||
name: name + "_itable",
|
||||
kind: asmKind(arch.ptrSize),
|
||||
typ: "interface itable",
|
||||
off: offset,
|
||||
size: arch.ptrSize,
|
||||
})
|
||||
addVar(name, asmVar{
|
||||
name: name + "_data",
|
||||
kind: asmKind(arch.ptrSize),
|
||||
typ: "interface data",
|
||||
off: offset + arch.ptrSize,
|
||||
size: arch.ptrSize,
|
||||
})
|
||||
|
||||
case asmSlice:
|
||||
addVar(name, asmVar{
|
||||
name: name + "_base",
|
||||
kind: asmKind(arch.ptrSize),
|
||||
typ: "slice base",
|
||||
off: offset,
|
||||
size: arch.ptrSize,
|
||||
})
|
||||
addVar(name, asmVar{
|
||||
name: name + "_len",
|
||||
kind: asmKind(arch.intSize),
|
||||
typ: "slice len",
|
||||
off: offset + arch.ptrSize,
|
||||
size: arch.intSize,
|
||||
})
|
||||
addVar(name, asmVar{
|
||||
name: name + "_cap",
|
||||
kind: asmKind(arch.intSize),
|
||||
typ: "slice cap",
|
||||
off: offset + arch.ptrSize + arch.intSize,
|
||||
size: arch.intSize,
|
||||
})
|
||||
|
||||
case asmString:
|
||||
addVar(name, asmVar{
|
||||
name: name + "_base",
|
||||
kind: asmKind(arch.ptrSize),
|
||||
typ: "string base",
|
||||
off: offset,
|
||||
size: arch.ptrSize,
|
||||
})
|
||||
addVar(name, asmVar{
|
||||
name: name + "_len",
|
||||
kind: asmKind(arch.intSize),
|
||||
typ: "string len",
|
||||
off: offset + arch.ptrSize,
|
||||
size: arch.intSize,
|
||||
})
|
||||
}
|
||||
offset += size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m := make(map[string]*asmFunc)
|
||||
for _, arch = range arches {
|
||||
fn = &asmFunc{
|
||||
arch: arch,
|
||||
vars: make(map[string]*asmVar),
|
||||
varByOffset: make(map[int]*asmVar),
|
||||
}
|
||||
offset = 0
|
||||
addParams(decl.Type.Params.List)
|
||||
if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
|
||||
offset += -offset & (arch.maxAlign - 1)
|
||||
addParams(decl.Type.Results.List)
|
||||
}
|
||||
fn.size = offset
|
||||
m[arch.name] = fn
|
||||
}
|
||||
|
||||
if failed {
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// asmCheckVar checks a single variable reference.
|
||||
func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) {
|
||||
m := asmOpcode.FindStringSubmatch(line)
|
||||
if m == nil {
|
||||
if !strings.HasPrefix(strings.TrimSpace(line), "//") {
|
||||
badf("cannot find assembly opcode")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Determine operand sizes from instruction.
|
||||
// Typically the suffix suffices, but there are exceptions.
|
||||
var src, dst, kind asmKind
|
||||
op := m[1]
|
||||
switch fn.arch.name + "." + op {
|
||||
case "386.FMOVLP":
|
||||
src, dst = 8, 4
|
||||
case "arm.MOVD":
|
||||
src = 8
|
||||
case "arm.MOVW":
|
||||
src = 4
|
||||
case "arm.MOVH", "arm.MOVHU":
|
||||
src = 2
|
||||
case "arm.MOVB", "arm.MOVBU":
|
||||
src = 1
|
||||
// LEA* opcodes don't really read the second arg.
|
||||
// They just take the address of it.
|
||||
case "386.LEAL":
|
||||
dst = 4
|
||||
case "amd64.LEAQ":
|
||||
dst = 8
|
||||
case "amd64p32.LEAL":
|
||||
dst = 4
|
||||
default:
|
||||
switch fn.arch.name {
|
||||
case "386", "amd64":
|
||||
if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) {
|
||||
// FMOVDP, FXCHD, etc
|
||||
src = 8
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") {
|
||||
// PINSRD, PEXTRD, etc
|
||||
src = 4
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
|
||||
// FMOVFP, FXCHF, etc
|
||||
src = 4
|
||||
break
|
||||
}
|
||||
if strings.HasSuffix(op, "SD") {
|
||||
// MOVSD, SQRTSD, etc
|
||||
src = 8
|
||||
break
|
||||
}
|
||||
if strings.HasSuffix(op, "SS") {
|
||||
// MOVSS, SQRTSS, etc
|
||||
src = 4
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(op, "SET") {
|
||||
// SETEQ, etc
|
||||
src = 1
|
||||
break
|
||||
}
|
||||
switch op[len(op)-1] {
|
||||
case 'B':
|
||||
src = 1
|
||||
case 'W':
|
||||
src = 2
|
||||
case 'L':
|
||||
src = 4
|
||||
case 'D', 'Q':
|
||||
src = 8
|
||||
}
|
||||
case "ppc64", "ppc64le":
|
||||
// Strip standard suffixes to reveal size letter.
|
||||
m := ppc64Suff.FindStringSubmatch(op)
|
||||
if m != nil {
|
||||
switch m[1][0] {
|
||||
case 'B':
|
||||
src = 1
|
||||
case 'H':
|
||||
src = 2
|
||||
case 'W':
|
||||
src = 4
|
||||
case 'D':
|
||||
src = 8
|
||||
}
|
||||
}
|
||||
case "mips64", "mips64le":
|
||||
switch op {
|
||||
case "MOVB", "MOVBU":
|
||||
src = 1
|
||||
case "MOVH", "MOVHU":
|
||||
src = 2
|
||||
case "MOVW", "MOVWU", "MOVF":
|
||||
src = 4
|
||||
case "MOVV", "MOVD":
|
||||
src = 8
|
||||
}
|
||||
}
|
||||
}
|
||||
if dst == 0 {
|
||||
dst = src
|
||||
}
|
||||
|
||||
// Determine whether the match we're holding
|
||||
// is the first or second argument.
|
||||
if strings.Index(line, expr) > strings.Index(line, ",") {
|
||||
kind = dst
|
||||
} else {
|
||||
kind = src
|
||||
}
|
||||
|
||||
vk := v.kind
|
||||
vt := v.typ
|
||||
switch vk {
|
||||
case asmInterface, asmEmptyInterface, asmString, asmSlice:
|
||||
// allow reference to first word (pointer)
|
||||
vk = v.inner[0].kind
|
||||
vt = v.inner[0].typ
|
||||
}
|
||||
|
||||
if off != v.off {
|
||||
var inner bytes.Buffer
|
||||
for i, vi := range v.inner {
|
||||
if len(v.inner) > 1 {
|
||||
fmt.Fprintf(&inner, ",")
|
||||
}
|
||||
fmt.Fprintf(&inner, " ")
|
||||
if i == len(v.inner)-1 {
|
||||
fmt.Fprintf(&inner, "or ")
|
||||
}
|
||||
fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
|
||||
}
|
||||
badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String())
|
||||
return
|
||||
}
|
||||
if kind != 0 && kind != vk {
|
||||
var inner bytes.Buffer
|
||||
if len(v.inner) > 0 {
|
||||
fmt.Fprintf(&inner, " containing")
|
||||
for i, vi := range v.inner {
|
||||
if i > 0 && len(v.inner) > 2 {
|
||||
fmt.Fprintf(&inner, ",")
|
||||
}
|
||||
fmt.Fprintf(&inner, " ")
|
||||
if i > 0 && i == len(v.inner)-1 {
|
||||
fmt.Fprintf(&inner, "and ")
|
||||
}
|
||||
fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
|
||||
}
|
||||
}
|
||||
badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vk, inner.String())
|
||||
}
|
||||
}
|
||||
49
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/assign.go
vendored
Normal file
49
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/assign.go
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// 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.
|
||||
|
||||
/*
|
||||
This file contains the code to check for useless assignments.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("assign",
|
||||
"check for useless assignments",
|
||||
checkAssignStmt,
|
||||
assignStmt)
|
||||
}
|
||||
|
||||
// TODO: should also check for assignments to struct fields inside methods
|
||||
// that are on T instead of *T.
|
||||
|
||||
// checkAssignStmt checks for assignments of the form "<expr> = <expr>".
|
||||
// These are almost always useless, and even when they aren't they are usually a mistake.
|
||||
func checkAssignStmt(f *File, node ast.Node) {
|
||||
stmt := node.(*ast.AssignStmt)
|
||||
if stmt.Tok != token.ASSIGN {
|
||||
return // ignore :=
|
||||
}
|
||||
if len(stmt.Lhs) != len(stmt.Rhs) {
|
||||
// If LHS and RHS have different cardinality, they can't be the same.
|
||||
return
|
||||
}
|
||||
for i, lhs := range stmt.Lhs {
|
||||
rhs := stmt.Rhs[i]
|
||||
if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) {
|
||||
continue // short-circuit the heavy-weight gofmt check
|
||||
}
|
||||
le := f.gofmt(lhs)
|
||||
re := f.gofmt(rhs)
|
||||
if le == re {
|
||||
f.Badf(stmt.Pos(), "self-assignment of %s to %s", re, le)
|
||||
}
|
||||
}
|
||||
}
|
||||
69
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/atomic.go
vendored
Normal file
69
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/atomic.go
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// 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 main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("atomic",
|
||||
"check for common mistaken usages of the sync/atomic package",
|
||||
checkAtomicAssignment,
|
||||
assignStmt)
|
||||
}
|
||||
|
||||
// checkAtomicAssignment walks the assignment statement checking for common
|
||||
// mistaken usage of atomic package, such as: x = atomic.AddUint64(&x, 1)
|
||||
func checkAtomicAssignment(f *File, node ast.Node) {
|
||||
n := node.(*ast.AssignStmt)
|
||||
if len(n.Lhs) != len(n.Rhs) {
|
||||
return
|
||||
}
|
||||
if len(n.Lhs) == 1 && n.Tok == token.DEFINE {
|
||||
return
|
||||
}
|
||||
|
||||
for i, right := range n.Rhs {
|
||||
call, ok := right.(*ast.CallExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
sel, ok := call.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
pkg, ok := sel.X.(*ast.Ident)
|
||||
if !ok || pkg.Name != "atomic" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch sel.Sel.Name {
|
||||
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
|
||||
f.checkAtomicAddAssignment(n.Lhs[i], call)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkAtomicAddAssignment walks the atomic.Add* method calls checking for assigning the return value
|
||||
// to the same variable being used in the operation
|
||||
func (f *File) checkAtomicAddAssignment(left ast.Expr, call *ast.CallExpr) {
|
||||
if len(call.Args) != 2 {
|
||||
return
|
||||
}
|
||||
arg := call.Args[0]
|
||||
broken := false
|
||||
|
||||
if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND {
|
||||
broken = f.gofmt(left) == f.gofmt(uarg.X)
|
||||
} else if star, ok := left.(*ast.StarExpr); ok {
|
||||
broken = f.gofmt(star.X) == f.gofmt(arg)
|
||||
}
|
||||
|
||||
if broken {
|
||||
f.Bad(left.Pos(), "direct assignment to atomic value")
|
||||
}
|
||||
}
|
||||
186
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/bool.go
vendored
Normal file
186
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/bool.go
vendored
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains boolean condition tests.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("bool",
|
||||
"check for mistakes involving boolean operators",
|
||||
checkBool,
|
||||
binaryExpr)
|
||||
}
|
||||
|
||||
func checkBool(f *File, n ast.Node) {
|
||||
e := n.(*ast.BinaryExpr)
|
||||
|
||||
var op boolOp
|
||||
switch e.Op {
|
||||
case token.LOR:
|
||||
op = or
|
||||
case token.LAND:
|
||||
op = and
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
comm := op.commutativeSets(e)
|
||||
for _, exprs := range comm {
|
||||
op.checkRedundant(f, exprs)
|
||||
op.checkSuspect(f, exprs)
|
||||
}
|
||||
}
|
||||
|
||||
type boolOp struct {
|
||||
name string
|
||||
tok token.Token // token corresponding to this operator
|
||||
badEq token.Token // token corresponding to the equality test that should not be used with this operator
|
||||
}
|
||||
|
||||
var (
|
||||
or = boolOp{"or", token.LOR, token.NEQ}
|
||||
and = boolOp{"and", token.LAND, token.EQL}
|
||||
)
|
||||
|
||||
// commutativeSets returns all side effect free sets of
|
||||
// expressions in e that are connected by op.
|
||||
// For example, given 'a || b || f() || c || d' with the or op,
|
||||
// commutativeSets returns {{b, a}, {d, c}}.
|
||||
func (op boolOp) commutativeSets(e *ast.BinaryExpr) [][]ast.Expr {
|
||||
exprs := op.split(e)
|
||||
|
||||
// Partition the slice of expressions into commutative sets.
|
||||
i := 0
|
||||
var sets [][]ast.Expr
|
||||
for j := 0; j <= len(exprs); j++ {
|
||||
if j == len(exprs) || hasSideEffects(exprs[j]) {
|
||||
if i < j {
|
||||
sets = append(sets, exprs[i:j])
|
||||
}
|
||||
i = j + 1
|
||||
}
|
||||
}
|
||||
|
||||
return sets
|
||||
}
|
||||
|
||||
// checkRedundant checks for expressions of the form
|
||||
// e && e
|
||||
// e || e
|
||||
// Exprs must contain only side effect free expressions.
|
||||
func (op boolOp) checkRedundant(f *File, exprs []ast.Expr) {
|
||||
seen := make(map[string]bool)
|
||||
for _, e := range exprs {
|
||||
efmt := f.gofmt(e)
|
||||
if seen[efmt] {
|
||||
f.Badf(e.Pos(), "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
|
||||
} else {
|
||||
seen[efmt] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkSuspect checks for expressions of the form
|
||||
// x != c1 || x != c2
|
||||
// x == c1 && x == c2
|
||||
// where c1 and c2 are constant expressions.
|
||||
// If c1 and c2 are the same then it's redundant;
|
||||
// if c1 and c2 are different then it's always true or always false.
|
||||
// Exprs must contain only side effect free expressions.
|
||||
func (op boolOp) checkSuspect(f *File, exprs []ast.Expr) {
|
||||
// seen maps from expressions 'x' to equality expressions 'x != c'.
|
||||
seen := make(map[string]string)
|
||||
|
||||
for _, e := range exprs {
|
||||
bin, ok := e.(*ast.BinaryExpr)
|
||||
if !ok || bin.Op != op.badEq {
|
||||
continue
|
||||
}
|
||||
|
||||
// In order to avoid false positives, restrict to cases
|
||||
// in which one of the operands is constant. We're then
|
||||
// interested in the other operand.
|
||||
// In the rare case in which both operands are constant
|
||||
// (e.g. runtime.GOOS and "windows"), we'll only catch
|
||||
// mistakes if the LHS is repeated, which is how most
|
||||
// code is written.
|
||||
var x ast.Expr
|
||||
switch {
|
||||
case f.pkg.types[bin.Y].Value != nil:
|
||||
x = bin.X
|
||||
case f.pkg.types[bin.X].Value != nil:
|
||||
x = bin.Y
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
// e is of the form 'x != c' or 'x == c'.
|
||||
xfmt := f.gofmt(x)
|
||||
efmt := f.gofmt(e)
|
||||
if prev, found := seen[xfmt]; found {
|
||||
// checkRedundant handles the case in which efmt == prev.
|
||||
if efmt != prev {
|
||||
f.Badf(e.Pos(), "suspect %s: %s %s %s", op.name, efmt, op.tok, prev)
|
||||
}
|
||||
} else {
|
||||
seen[xfmt] = efmt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hasSideEffects reports whether evaluation of e has side effects.
|
||||
func hasSideEffects(e ast.Expr) bool {
|
||||
safe := true
|
||||
ast.Inspect(e, func(node ast.Node) bool {
|
||||
switch n := node.(type) {
|
||||
// Using CallExpr here will catch conversions
|
||||
// as well as function and method invocations.
|
||||
// We'll live with the false negatives for now.
|
||||
case *ast.CallExpr:
|
||||
safe = false
|
||||
return false
|
||||
case *ast.UnaryExpr:
|
||||
if n.Op == token.ARROW {
|
||||
safe = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return !safe
|
||||
}
|
||||
|
||||
// split returns a slice of all subexpressions in e that are connected by op.
|
||||
// For example, given 'a || (b || c) || d' with the or op,
|
||||
// split returns []{d, c, b, a}.
|
||||
func (op boolOp) split(e ast.Expr) (exprs []ast.Expr) {
|
||||
for {
|
||||
e = unparen(e)
|
||||
if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
|
||||
exprs = append(exprs, op.split(b.Y)...)
|
||||
e = b.X
|
||||
} else {
|
||||
exprs = append(exprs, e)
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
91
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/buildtag.go
vendored
Normal file
91
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/buildtag.go
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
nl = []byte("\n")
|
||||
slashSlash = []byte("//")
|
||||
plusBuild = []byte("+build")
|
||||
)
|
||||
|
||||
// checkBuildTag checks that build tags are in the correct location and well-formed.
|
||||
func checkBuildTag(name string, data []byte) {
|
||||
if !vet("buildtags") {
|
||||
return
|
||||
}
|
||||
lines := bytes.SplitAfter(data, nl)
|
||||
|
||||
// Determine cutpoint where +build comments are no longer valid.
|
||||
// They are valid in leading // comments in the file followed by
|
||||
// a blank line.
|
||||
var cutoff int
|
||||
for i, line := range lines {
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
cutoff = i
|
||||
continue
|
||||
}
|
||||
if bytes.HasPrefix(line, slashSlash) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
for i, line := range lines {
|
||||
line = bytes.TrimSpace(line)
|
||||
if !bytes.HasPrefix(line, slashSlash) {
|
||||
continue
|
||||
}
|
||||
text := bytes.TrimSpace(line[2:])
|
||||
if bytes.HasPrefix(text, plusBuild) {
|
||||
fields := bytes.Fields(text)
|
||||
if !bytes.Equal(fields[0], plusBuild) {
|
||||
// Comment is something like +buildasdf not +build.
|
||||
fmt.Fprintf(os.Stderr, "%s:%d: possible malformed +build comment\n", name, i+1)
|
||||
continue
|
||||
}
|
||||
if i >= cutoff {
|
||||
fmt.Fprintf(os.Stderr, "%s:%d: +build comment must appear before package clause and be followed by a blank line\n", name, i+1)
|
||||
setExit(1)
|
||||
continue
|
||||
}
|
||||
// Check arguments.
|
||||
Args:
|
||||
for _, arg := range fields[1:] {
|
||||
for _, elem := range strings.Split(string(arg), ",") {
|
||||
if strings.HasPrefix(elem, "!!") {
|
||||
fmt.Fprintf(os.Stderr, "%s:%d: invalid double negative in build constraint: %s\n", name, i+1, arg)
|
||||
setExit(1)
|
||||
break Args
|
||||
}
|
||||
if strings.HasPrefix(elem, "!") {
|
||||
elem = elem[1:]
|
||||
}
|
||||
for _, c := range elem {
|
||||
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
|
||||
fmt.Fprintf(os.Stderr, "%s:%d: invalid non-alphanumeric build constraint: %s\n", name, i+1, arg)
|
||||
setExit(1)
|
||||
break Args
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Comment with +build but not at beginning.
|
||||
if bytes.Contains(line, plusBuild) && i < cutoff {
|
||||
fmt.Fprintf(os.Stderr, "%s:%d: possible malformed +build comment\n", name, i+1)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
130
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/cgo.go
vendored
Normal file
130
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/cgo.go
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// 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.
|
||||
|
||||
// Check for invalid cgo pointer passing.
|
||||
// This looks for code that uses cgo to call C code passing values
|
||||
// whose types are almost always invalid according to the cgo pointer
|
||||
// sharing rules.
|
||||
// Specifically, it warns about attempts to pass a Go chan, map, func,
|
||||
// or slice to C, either directly, or via a pointer, array, or struct.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("cgocall",
|
||||
"check for types that may not be passed to cgo calls",
|
||||
checkCgoCall,
|
||||
callExpr)
|
||||
}
|
||||
|
||||
func checkCgoCall(f *File, node ast.Node) {
|
||||
x := node.(*ast.CallExpr)
|
||||
|
||||
// We are only looking for calls to functions imported from
|
||||
// the "C" package.
|
||||
sel, ok := x.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
id, ok := sel.X.(*ast.Ident)
|
||||
if !ok || id.Name != "C" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, arg := range x.Args {
|
||||
if !typeOKForCgoCall(cgoBaseType(f, arg)) {
|
||||
f.Badf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
|
||||
}
|
||||
|
||||
// Check for passing the address of a bad type.
|
||||
if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 && f.hasBasicType(conv.Fun, types.UnsafePointer) {
|
||||
arg = conv.Args[0]
|
||||
}
|
||||
if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND {
|
||||
if !typeOKForCgoCall(cgoBaseType(f, u.X)) {
|
||||
f.Badf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cgoBaseType tries to look through type conversions involving
|
||||
// unsafe.Pointer to find the real type. It converts:
|
||||
// unsafe.Pointer(x) => x
|
||||
// *(*unsafe.Pointer)(unsafe.Pointer(&x)) => x
|
||||
func cgoBaseType(f *File, arg ast.Expr) types.Type {
|
||||
switch arg := arg.(type) {
|
||||
case *ast.CallExpr:
|
||||
if len(arg.Args) == 1 && f.hasBasicType(arg.Fun, types.UnsafePointer) {
|
||||
return cgoBaseType(f, arg.Args[0])
|
||||
}
|
||||
case *ast.StarExpr:
|
||||
call, ok := arg.X.(*ast.CallExpr)
|
||||
if !ok || len(call.Args) != 1 {
|
||||
break
|
||||
}
|
||||
// Here arg is *f(v).
|
||||
t := f.pkg.types[call.Fun].Type
|
||||
if t == nil {
|
||||
break
|
||||
}
|
||||
ptr, ok := t.Underlying().(*types.Pointer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
// Here arg is *(*p)(v)
|
||||
elem, ok := ptr.Elem().Underlying().(*types.Basic)
|
||||
if !ok || elem.Kind() != types.UnsafePointer {
|
||||
break
|
||||
}
|
||||
// Here arg is *(*unsafe.Pointer)(v)
|
||||
call, ok = call.Args[0].(*ast.CallExpr)
|
||||
if !ok || len(call.Args) != 1 {
|
||||
break
|
||||
}
|
||||
// Here arg is *(*unsafe.Pointer)(f(v))
|
||||
if !f.hasBasicType(call.Fun, types.UnsafePointer) {
|
||||
break
|
||||
}
|
||||
// Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v))
|
||||
u, ok := call.Args[0].(*ast.UnaryExpr)
|
||||
if !ok || u.Op != token.AND {
|
||||
break
|
||||
}
|
||||
// Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v))
|
||||
return cgoBaseType(f, u.X)
|
||||
}
|
||||
|
||||
return f.pkg.types[arg].Type
|
||||
}
|
||||
|
||||
// typeOKForCgoCall returns true if the type of arg is OK to pass to a
|
||||
// C function using cgo. This is not true for Go types with embedded
|
||||
// pointers.
|
||||
func typeOKForCgoCall(t types.Type) bool {
|
||||
if t == nil {
|
||||
return true
|
||||
}
|
||||
switch t := t.Underlying().(type) {
|
||||
case *types.Chan, *types.Map, *types.Signature, *types.Slice:
|
||||
return false
|
||||
case *types.Pointer:
|
||||
return typeOKForCgoCall(t.Elem())
|
||||
case *types.Array:
|
||||
return typeOKForCgoCall(t.Elem())
|
||||
case *types.Struct:
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
if !typeOKForCgoCall(t.Field(i).Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
82
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/composite.go
vendored
Normal file
82
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/composite.go
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains the test for unkeyed struct literals.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/dnephin/govet/internal/whitelist"
|
||||
"flag"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var compositeWhiteList = flag.Bool("compositewhitelist", true, "use composite white list; for testing only")
|
||||
|
||||
func init() {
|
||||
register("composites",
|
||||
"check that composite literals used field-keyed elements",
|
||||
checkUnkeyedLiteral,
|
||||
compositeLit)
|
||||
}
|
||||
|
||||
// checkUnkeyedLiteral checks if a composite literal is a struct literal with
|
||||
// unkeyed fields.
|
||||
func checkUnkeyedLiteral(f *File, node ast.Node) {
|
||||
cl := node.(*ast.CompositeLit)
|
||||
|
||||
typ := f.pkg.types[cl].Type
|
||||
if typ == nil {
|
||||
// cannot determine composite literals' type, skip it
|
||||
return
|
||||
}
|
||||
typeName := typ.String()
|
||||
if *compositeWhiteList && whitelist.UnkeyedLiteral[typeName] {
|
||||
// skip whitelisted types
|
||||
return
|
||||
}
|
||||
if _, ok := typ.Underlying().(*types.Struct); !ok {
|
||||
// skip non-struct composite literals
|
||||
return
|
||||
}
|
||||
if isLocalType(f, typeName) {
|
||||
// allow unkeyed locally defined composite literal
|
||||
return
|
||||
}
|
||||
|
||||
// check if the CompositeLit contains an unkeyed field
|
||||
allKeyValue := true
|
||||
for _, e := range cl.Elts {
|
||||
if _, ok := e.(*ast.KeyValueExpr); !ok {
|
||||
allKeyValue = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allKeyValue {
|
||||
// all the composite literal fields are keyed
|
||||
return
|
||||
}
|
||||
|
||||
f.Badf(cl.Pos(), "%s composite literal uses unkeyed fields", typeName)
|
||||
}
|
||||
|
||||
func isLocalType(f *File, typeName string) bool {
|
||||
if strings.HasPrefix(typeName, "struct{") {
|
||||
// struct literals are local types
|
||||
return true
|
||||
}
|
||||
|
||||
pkgname := f.pkg.path
|
||||
if strings.HasPrefix(typeName, pkgname+".") {
|
||||
return true
|
||||
}
|
||||
|
||||
// treat types as local inside test packages with _test name suffix
|
||||
if strings.HasSuffix(pkgname, "_test") {
|
||||
pkgname = pkgname[:len(pkgname)-len("_test")]
|
||||
}
|
||||
return strings.HasPrefix(typeName, pkgname+".")
|
||||
}
|
||||
239
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/copylock.go
vendored
Normal file
239
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/copylock.go
vendored
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains the code to check that locks are not passed by value.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("copylocks",
|
||||
"check that locks are not passed by value",
|
||||
checkCopyLocks,
|
||||
funcDecl, rangeStmt, funcLit, callExpr, assignStmt, genDecl, compositeLit, returnStmt)
|
||||
}
|
||||
|
||||
// checkCopyLocks checks whether node might
|
||||
// inadvertently copy a lock.
|
||||
func checkCopyLocks(f *File, node ast.Node) {
|
||||
switch node := node.(type) {
|
||||
case *ast.RangeStmt:
|
||||
checkCopyLocksRange(f, node)
|
||||
case *ast.FuncDecl:
|
||||
checkCopyLocksFunc(f, node.Name.Name, node.Recv, node.Type)
|
||||
case *ast.FuncLit:
|
||||
checkCopyLocksFunc(f, "func", nil, node.Type)
|
||||
case *ast.CallExpr:
|
||||
checkCopyLocksCallExpr(f, node)
|
||||
case *ast.AssignStmt:
|
||||
checkCopyLocksAssign(f, node)
|
||||
case *ast.GenDecl:
|
||||
checkCopyLocksGenDecl(f, node)
|
||||
case *ast.CompositeLit:
|
||||
checkCopyLocksCompositeLit(f, node)
|
||||
case *ast.ReturnStmt:
|
||||
checkCopyLocksReturnStmt(f, node)
|
||||
}
|
||||
}
|
||||
|
||||
// checkCopyLocksAssign checks whether an assignment
|
||||
// copies a lock.
|
||||
func checkCopyLocksAssign(f *File, as *ast.AssignStmt) {
|
||||
for i, x := range as.Rhs {
|
||||
if path := lockPathRhs(f, x); path != nil {
|
||||
f.Badf(x.Pos(), "assignment copies lock value to %v: %v", f.gofmt(as.Lhs[i]), path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkCopyLocksGenDecl checks whether lock is copied
|
||||
// in variable declaration.
|
||||
func checkCopyLocksGenDecl(f *File, gd *ast.GenDecl) {
|
||||
if gd.Tok != token.VAR {
|
||||
return
|
||||
}
|
||||
for _, spec := range gd.Specs {
|
||||
valueSpec := spec.(*ast.ValueSpec)
|
||||
for i, x := range valueSpec.Values {
|
||||
if path := lockPathRhs(f, x); path != nil {
|
||||
f.Badf(x.Pos(), "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkCopyLocksCompositeLit detects lock copy inside a composite literal
|
||||
func checkCopyLocksCompositeLit(f *File, cl *ast.CompositeLit) {
|
||||
for _, x := range cl.Elts {
|
||||
if node, ok := x.(*ast.KeyValueExpr); ok {
|
||||
x = node.Value
|
||||
}
|
||||
if path := lockPathRhs(f, x); path != nil {
|
||||
f.Badf(x.Pos(), "literal copies lock value from %v: %v", f.gofmt(x), path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkCopyLocksReturnStmt detects lock copy in return statement
|
||||
func checkCopyLocksReturnStmt(f *File, rs *ast.ReturnStmt) {
|
||||
for _, x := range rs.Results {
|
||||
if path := lockPathRhs(f, x); path != nil {
|
||||
f.Badf(x.Pos(), "return copies lock value: %v", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkCopyLocksCallExpr detects lock copy in the arguments to a function call
|
||||
func checkCopyLocksCallExpr(f *File, ce *ast.CallExpr) {
|
||||
if id, ok := ce.Fun.(*ast.Ident); ok && id.Name == "new" && f.pkg.types[id].IsBuiltin() {
|
||||
// Skip 'new(Type)' for built-in 'new'
|
||||
return
|
||||
}
|
||||
for _, x := range ce.Args {
|
||||
if path := lockPathRhs(f, x); path != nil {
|
||||
f.Badf(x.Pos(), "function call copies lock value: %v", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkCopyLocksFunc checks whether a function might
|
||||
// inadvertently copy a lock, by checking whether
|
||||
// its receiver, parameters, or return values
|
||||
// are locks.
|
||||
func checkCopyLocksFunc(f *File, name string, recv *ast.FieldList, typ *ast.FuncType) {
|
||||
if recv != nil && len(recv.List) > 0 {
|
||||
expr := recv.List[0].Type
|
||||
if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil {
|
||||
f.Badf(expr.Pos(), "%s passes lock by value: %v", name, path)
|
||||
}
|
||||
}
|
||||
|
||||
if typ.Params != nil {
|
||||
for _, field := range typ.Params.List {
|
||||
expr := field.Type
|
||||
if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil {
|
||||
f.Badf(expr.Pos(), "%s passes lock by value: %v", name, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't check typ.Results. If T has a Lock field it's OK to write
|
||||
// return T{}
|
||||
// because that is returning the zero value. Leave result checking
|
||||
// to the return statement.
|
||||
}
|
||||
|
||||
// checkCopyLocksRange checks whether a range statement
|
||||
// might inadvertently copy a lock by checking whether
|
||||
// any of the range variables are locks.
|
||||
func checkCopyLocksRange(f *File, r *ast.RangeStmt) {
|
||||
checkCopyLocksRangeVar(f, r.Tok, r.Key)
|
||||
checkCopyLocksRangeVar(f, r.Tok, r.Value)
|
||||
}
|
||||
|
||||
func checkCopyLocksRangeVar(f *File, rtok token.Token, e ast.Expr) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
id, isId := e.(*ast.Ident)
|
||||
if isId && id.Name == "_" {
|
||||
return
|
||||
}
|
||||
|
||||
var typ types.Type
|
||||
if rtok == token.DEFINE {
|
||||
if !isId {
|
||||
return
|
||||
}
|
||||
obj := f.pkg.defs[id]
|
||||
if obj == nil {
|
||||
return
|
||||
}
|
||||
typ = obj.Type()
|
||||
} else {
|
||||
typ = f.pkg.types[e].Type
|
||||
}
|
||||
|
||||
if typ == nil {
|
||||
return
|
||||
}
|
||||
if path := lockPath(f.pkg.typesPkg, typ); path != nil {
|
||||
f.Badf(e.Pos(), "range var %s copies lock: %v", f.gofmt(e), path)
|
||||
}
|
||||
}
|
||||
|
||||
type typePath []types.Type
|
||||
|
||||
// String pretty-prints a typePath.
|
||||
func (path typePath) String() string {
|
||||
n := len(path)
|
||||
var buf bytes.Buffer
|
||||
for i := range path {
|
||||
if i > 0 {
|
||||
fmt.Fprint(&buf, " contains ")
|
||||
}
|
||||
// The human-readable path is in reverse order, outermost to innermost.
|
||||
fmt.Fprint(&buf, path[n-i-1].String())
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func lockPathRhs(f *File, x ast.Expr) typePath {
|
||||
if _, ok := x.(*ast.CompositeLit); ok {
|
||||
return nil
|
||||
}
|
||||
if _, ok := x.(*ast.CallExpr); ok {
|
||||
// A call may return a zero value.
|
||||
return nil
|
||||
}
|
||||
if star, ok := x.(*ast.StarExpr); ok {
|
||||
if _, ok := star.X.(*ast.CallExpr); ok {
|
||||
// A call may return a pointer to a zero value.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return lockPath(f.pkg.typesPkg, f.pkg.types[x].Type)
|
||||
}
|
||||
|
||||
// lockPath returns a typePath describing the location of a lock value
|
||||
// contained in typ. If there is no contained lock, it returns nil.
|
||||
func lockPath(tpkg *types.Package, typ types.Type) typePath {
|
||||
if typ == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We're only interested in the case in which the underlying
|
||||
// type is a struct. (Interfaces and pointers are safe to copy.)
|
||||
styp, ok := typ.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We're looking for cases in which a reference to this type
|
||||
// can be locked, but a value cannot. This differentiates
|
||||
// embedded interfaces from embedded values.
|
||||
if plock := types.NewMethodSet(types.NewPointer(typ)).Lookup(tpkg, "Lock"); plock != nil {
|
||||
if lock := types.NewMethodSet(typ).Lookup(tpkg, "Lock"); lock == nil {
|
||||
return []types.Type{typ}
|
||||
}
|
||||
}
|
||||
|
||||
nfields := styp.NumFields()
|
||||
for i := 0; i < nfields; i++ {
|
||||
ftyp := styp.Field(i).Type()
|
||||
subpath := lockPath(tpkg, ftyp)
|
||||
if subpath != nil {
|
||||
return append(subpath, typ)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
298
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/deadcode.go
vendored
Normal file
298
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/deadcode.go
vendored
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
// 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.
|
||||
|
||||
// Check for syntactically unreachable code.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("unreachable",
|
||||
"check for unreachable code",
|
||||
checkUnreachable,
|
||||
funcDecl, funcLit)
|
||||
}
|
||||
|
||||
type deadState struct {
|
||||
f *File
|
||||
hasBreak map[ast.Stmt]bool
|
||||
hasGoto map[string]bool
|
||||
labels map[string]ast.Stmt
|
||||
breakTarget ast.Stmt
|
||||
|
||||
reachable bool
|
||||
}
|
||||
|
||||
// checkUnreachable checks a function body for dead code.
|
||||
//
|
||||
// TODO(adonovan): use the new cfg package, which is more precise.
|
||||
func checkUnreachable(f *File, node ast.Node) {
|
||||
var body *ast.BlockStmt
|
||||
switch n := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
body = n.Body
|
||||
case *ast.FuncLit:
|
||||
body = n.Body
|
||||
}
|
||||
if body == nil {
|
||||
return
|
||||
}
|
||||
|
||||
d := &deadState{
|
||||
f: f,
|
||||
hasBreak: make(map[ast.Stmt]bool),
|
||||
hasGoto: make(map[string]bool),
|
||||
labels: make(map[string]ast.Stmt),
|
||||
}
|
||||
|
||||
d.findLabels(body)
|
||||
|
||||
d.reachable = true
|
||||
d.findDead(body)
|
||||
}
|
||||
|
||||
// findLabels gathers information about the labels defined and used by stmt
|
||||
// and about which statements break, whether a label is involved or not.
|
||||
func (d *deadState) findLabels(stmt ast.Stmt) {
|
||||
switch x := stmt.(type) {
|
||||
default:
|
||||
d.f.Warnf(x.Pos(), "internal error in findLabels: unexpected statement %T", x)
|
||||
|
||||
case *ast.AssignStmt,
|
||||
*ast.BadStmt,
|
||||
*ast.DeclStmt,
|
||||
*ast.DeferStmt,
|
||||
*ast.EmptyStmt,
|
||||
*ast.ExprStmt,
|
||||
*ast.GoStmt,
|
||||
*ast.IncDecStmt,
|
||||
*ast.ReturnStmt,
|
||||
*ast.SendStmt:
|
||||
// no statements inside
|
||||
|
||||
case *ast.BlockStmt:
|
||||
for _, stmt := range x.List {
|
||||
d.findLabels(stmt)
|
||||
}
|
||||
|
||||
case *ast.BranchStmt:
|
||||
switch x.Tok {
|
||||
case token.GOTO:
|
||||
if x.Label != nil {
|
||||
d.hasGoto[x.Label.Name] = true
|
||||
}
|
||||
|
||||
case token.BREAK:
|
||||
stmt := d.breakTarget
|
||||
if x.Label != nil {
|
||||
stmt = d.labels[x.Label.Name]
|
||||
}
|
||||
if stmt != nil {
|
||||
d.hasBreak[stmt] = true
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.IfStmt:
|
||||
d.findLabels(x.Body)
|
||||
if x.Else != nil {
|
||||
d.findLabels(x.Else)
|
||||
}
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
d.labels[x.Label.Name] = x.Stmt
|
||||
d.findLabels(x.Stmt)
|
||||
|
||||
// These cases are all the same, but the x.Body only works
|
||||
// when the specific type of x is known, so the cases cannot
|
||||
// be merged.
|
||||
case *ast.ForStmt:
|
||||
outer := d.breakTarget
|
||||
d.breakTarget = x
|
||||
d.findLabels(x.Body)
|
||||
d.breakTarget = outer
|
||||
|
||||
case *ast.RangeStmt:
|
||||
outer := d.breakTarget
|
||||
d.breakTarget = x
|
||||
d.findLabels(x.Body)
|
||||
d.breakTarget = outer
|
||||
|
||||
case *ast.SelectStmt:
|
||||
outer := d.breakTarget
|
||||
d.breakTarget = x
|
||||
d.findLabels(x.Body)
|
||||
d.breakTarget = outer
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
outer := d.breakTarget
|
||||
d.breakTarget = x
|
||||
d.findLabels(x.Body)
|
||||
d.breakTarget = outer
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
outer := d.breakTarget
|
||||
d.breakTarget = x
|
||||
d.findLabels(x.Body)
|
||||
d.breakTarget = outer
|
||||
|
||||
case *ast.CommClause:
|
||||
for _, stmt := range x.Body {
|
||||
d.findLabels(stmt)
|
||||
}
|
||||
|
||||
case *ast.CaseClause:
|
||||
for _, stmt := range x.Body {
|
||||
d.findLabels(stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findDead walks the statement looking for dead code.
|
||||
// If d.reachable is false on entry, stmt itself is dead.
|
||||
// When findDead returns, d.reachable tells whether the
|
||||
// statement following stmt is reachable.
|
||||
func (d *deadState) findDead(stmt ast.Stmt) {
|
||||
// Is this a labeled goto target?
|
||||
// If so, assume it is reachable due to the goto.
|
||||
// This is slightly conservative, in that we don't
|
||||
// check that the goto is reachable, so
|
||||
// L: goto L
|
||||
// will not provoke a warning.
|
||||
// But it's good enough.
|
||||
if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] {
|
||||
d.reachable = true
|
||||
}
|
||||
|
||||
if !d.reachable {
|
||||
switch stmt.(type) {
|
||||
case *ast.EmptyStmt:
|
||||
// do not warn about unreachable empty statements
|
||||
default:
|
||||
d.f.Bad(stmt.Pos(), "unreachable code")
|
||||
d.reachable = true // silence error about next statement
|
||||
}
|
||||
}
|
||||
|
||||
switch x := stmt.(type) {
|
||||
default:
|
||||
d.f.Warnf(x.Pos(), "internal error in findDead: unexpected statement %T", x)
|
||||
|
||||
case *ast.AssignStmt,
|
||||
*ast.BadStmt,
|
||||
*ast.DeclStmt,
|
||||
*ast.DeferStmt,
|
||||
*ast.EmptyStmt,
|
||||
*ast.GoStmt,
|
||||
*ast.IncDecStmt,
|
||||
*ast.SendStmt:
|
||||
// no control flow
|
||||
|
||||
case *ast.BlockStmt:
|
||||
for _, stmt := range x.List {
|
||||
d.findDead(stmt)
|
||||
}
|
||||
|
||||
case *ast.BranchStmt:
|
||||
switch x.Tok {
|
||||
case token.BREAK, token.GOTO, token.FALLTHROUGH:
|
||||
d.reachable = false
|
||||
case token.CONTINUE:
|
||||
// NOTE: We accept "continue" statements as terminating.
|
||||
// They are not necessary in the spec definition of terminating,
|
||||
// because a continue statement cannot be the final statement
|
||||
// before a return. But for the more general problem of syntactically
|
||||
// identifying dead code, continue redirects control flow just
|
||||
// like the other terminating statements.
|
||||
d.reachable = false
|
||||
}
|
||||
|
||||
case *ast.ExprStmt:
|
||||
// Call to panic?
|
||||
call, ok := x.X.(*ast.CallExpr)
|
||||
if ok {
|
||||
name, ok := call.Fun.(*ast.Ident)
|
||||
if ok && name.Name == "panic" && name.Obj == nil {
|
||||
d.reachable = false
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.ForStmt:
|
||||
d.findDead(x.Body)
|
||||
d.reachable = x.Cond != nil || d.hasBreak[x]
|
||||
|
||||
case *ast.IfStmt:
|
||||
d.findDead(x.Body)
|
||||
if x.Else != nil {
|
||||
r := d.reachable
|
||||
d.reachable = true
|
||||
d.findDead(x.Else)
|
||||
d.reachable = d.reachable || r
|
||||
} else {
|
||||
// might not have executed if statement
|
||||
d.reachable = true
|
||||
}
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
d.findDead(x.Stmt)
|
||||
|
||||
case *ast.RangeStmt:
|
||||
d.findDead(x.Body)
|
||||
d.reachable = true
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
d.reachable = false
|
||||
|
||||
case *ast.SelectStmt:
|
||||
// NOTE: Unlike switch and type switch below, we don't care
|
||||
// whether a select has a default, because a select without a
|
||||
// default blocks until one of the cases can run. That's different
|
||||
// from a switch without a default, which behaves like it has
|
||||
// a default with an empty body.
|
||||
anyReachable := false
|
||||
for _, comm := range x.Body.List {
|
||||
d.reachable = true
|
||||
for _, stmt := range comm.(*ast.CommClause).Body {
|
||||
d.findDead(stmt)
|
||||
}
|
||||
anyReachable = anyReachable || d.reachable
|
||||
}
|
||||
d.reachable = anyReachable || d.hasBreak[x]
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
anyReachable := false
|
||||
hasDefault := false
|
||||
for _, cas := range x.Body.List {
|
||||
cc := cas.(*ast.CaseClause)
|
||||
if cc.List == nil {
|
||||
hasDefault = true
|
||||
}
|
||||
d.reachable = true
|
||||
for _, stmt := range cc.Body {
|
||||
d.findDead(stmt)
|
||||
}
|
||||
anyReachable = anyReachable || d.reachable
|
||||
}
|
||||
d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
anyReachable := false
|
||||
hasDefault := false
|
||||
for _, cas := range x.Body.List {
|
||||
cc := cas.(*ast.CaseClause)
|
||||
if cc.List == nil {
|
||||
hasDefault = true
|
||||
}
|
||||
d.reachable = true
|
||||
for _, stmt := range cc.Body {
|
||||
d.findDead(stmt)
|
||||
}
|
||||
anyReachable = anyReachable || d.reachable
|
||||
}
|
||||
d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
|
||||
}
|
||||
}
|
||||
205
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/doc.go
vendored
Normal file
205
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/doc.go
vendored
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Vet examines Go source code and reports suspicious constructs, such as Printf
|
||||
calls whose arguments do not align with the format string. Vet uses heuristics
|
||||
that do not guarantee all reports are genuine problems, but it can find errors
|
||||
not caught by the compilers.
|
||||
|
||||
It can be invoked three ways:
|
||||
|
||||
By package, from the go tool:
|
||||
go vet package/path/name
|
||||
vets the package whose path is provided.
|
||||
|
||||
By files:
|
||||
go tool vet source/directory/*.go
|
||||
vets the files named, all of which must be in the same package.
|
||||
|
||||
By directory:
|
||||
go tool vet source/directory
|
||||
recursively descends the directory, vetting each package it finds.
|
||||
|
||||
Vet's exit code is 2 for erroneous invocation of the tool, 1 if a
|
||||
problem was reported, and 0 otherwise. Note that the tool does not
|
||||
check every possible problem and depends on unreliable heuristics
|
||||
so it should be used as guidance only, not as a firm indicator of
|
||||
program correctness.
|
||||
|
||||
By default the -all flag is set so all checks are performed.
|
||||
If any flags are explicitly set to true, only those tests are run. Conversely, if
|
||||
any flag is explicitly set to false, only those tests are disabled. Thus -printf=true
|
||||
runs the printf check, -printf=false runs all checks except the printf check.
|
||||
|
||||
Available checks:
|
||||
|
||||
Assembly declarations
|
||||
|
||||
Flag: -asmdecl
|
||||
|
||||
Mismatches between assembly files and Go function declarations.
|
||||
|
||||
Useless assignments
|
||||
|
||||
Flag: -assign
|
||||
|
||||
Check for useless assignments.
|
||||
|
||||
Atomic mistakes
|
||||
|
||||
Flag: -atomic
|
||||
|
||||
Common mistaken usages of the sync/atomic package.
|
||||
|
||||
Boolean conditions
|
||||
|
||||
Flag: -bool
|
||||
|
||||
Mistakes involving boolean operators.
|
||||
|
||||
Build tags
|
||||
|
||||
Flag: -buildtags
|
||||
|
||||
Badly formed or misplaced +build tags.
|
||||
|
||||
Invalid uses of cgo
|
||||
|
||||
Flag: -cgocall
|
||||
|
||||
Detect some violations of the cgo pointer passing rules.
|
||||
|
||||
Unkeyed composite literals
|
||||
|
||||
Flag: -composites
|
||||
|
||||
Composite struct literals that do not use the field-keyed syntax.
|
||||
|
||||
Copying locks
|
||||
|
||||
Flag: -copylocks
|
||||
|
||||
Locks that are erroneously passed by value.
|
||||
|
||||
Tests, benchmarks and documentation examples
|
||||
|
||||
Flag: -tests
|
||||
|
||||
Mistakes involving tests including functions with incorrect names or signatures
|
||||
and example tests that document identifiers not in the package.
|
||||
|
||||
Failure to call the cancelation function returned by context.WithCancel.
|
||||
|
||||
Flag: -lostcancel
|
||||
|
||||
The cancelation function returned by context.WithCancel, WithTimeout,
|
||||
and WithDeadline must be called or the new context will remain live
|
||||
until its parent context is cancelled.
|
||||
(The background context is never cancelled.)
|
||||
|
||||
Methods
|
||||
|
||||
Flag: -methods
|
||||
|
||||
Non-standard signatures for methods with familiar names, including:
|
||||
Format GobEncode GobDecode MarshalJSON MarshalXML
|
||||
Peek ReadByte ReadFrom ReadRune Scan Seek
|
||||
UnmarshalJSON UnreadByte UnreadRune WriteByte
|
||||
WriteTo
|
||||
|
||||
Nil function comparison
|
||||
|
||||
Flag: -nilfunc
|
||||
|
||||
Comparisons between functions and nil.
|
||||
|
||||
Printf family
|
||||
|
||||
Flag: -printf
|
||||
|
||||
Suspicious calls to functions in the Printf family, including any functions
|
||||
with these names, disregarding case:
|
||||
Print Printf Println
|
||||
Fprint Fprintf Fprintln
|
||||
Sprint Sprintf Sprintln
|
||||
Error Errorf
|
||||
Fatal Fatalf
|
||||
Log Logf
|
||||
Panic Panicf Panicln
|
||||
The -printfuncs flag can be used to redefine this list.
|
||||
If the function name ends with an 'f', the function is assumed to take
|
||||
a format descriptor string in the manner of fmt.Printf. If not, vet
|
||||
complains about arguments that look like format descriptor strings.
|
||||
|
||||
It also checks for errors such as using a Writer as the first argument of
|
||||
Printf.
|
||||
|
||||
Struct tags
|
||||
|
||||
Range loop variables
|
||||
|
||||
Flag: -rangeloops
|
||||
|
||||
Incorrect uses of range loop variables in closures.
|
||||
|
||||
Shadowed variables
|
||||
|
||||
Flag: -shadow=false (experimental; must be set explicitly)
|
||||
|
||||
Variables that may have been unintentionally shadowed.
|
||||
|
||||
Shifts
|
||||
|
||||
Flag: -shift
|
||||
|
||||
Shifts equal to or longer than the variable's length.
|
||||
|
||||
Flag: -structtags
|
||||
|
||||
Struct tags that do not follow the format understood by reflect.StructTag.Get.
|
||||
Well-known encoding struct tags (json, xml) used with unexported fields.
|
||||
|
||||
Unreachable code
|
||||
|
||||
Flag: -unreachable
|
||||
|
||||
Unreachable code.
|
||||
|
||||
Misuse of unsafe Pointers
|
||||
|
||||
Flag: -unsafeptr
|
||||
|
||||
Likely incorrect uses of unsafe.Pointer to convert integers to pointers.
|
||||
A conversion from uintptr to unsafe.Pointer is invalid if it implies that
|
||||
there is a uintptr-typed word in memory that holds a pointer value,
|
||||
because that word will be invisible to stack copying and to the garbage
|
||||
collector.
|
||||
|
||||
Unused result of certain function calls
|
||||
|
||||
Flag: -unusedresult
|
||||
|
||||
Calls to well-known functions and methods that return a value that is
|
||||
discarded. By default, this includes functions like fmt.Errorf and
|
||||
fmt.Sprintf and methods like String and Error. The flags -unusedfuncs
|
||||
and -unusedstringmethods control the set.
|
||||
|
||||
Other flags
|
||||
|
||||
These flags configure the behavior of vet:
|
||||
|
||||
-all (default true)
|
||||
Enable all non-experimental checks.
|
||||
-v
|
||||
Verbose mode
|
||||
-printfuncs
|
||||
A comma-separated list of print-like function names
|
||||
to supplement the standard list.
|
||||
For more information, see the discussion of the -printf flag.
|
||||
-shadowstrict
|
||||
Whether to be strict about shadowing; can be noisy.
|
||||
*/
|
||||
package main
|
||||
|
|
@ -0,0 +1,512 @@
|
|||
// 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 {
|
||||
cfg *CFG
|
||||
mayReturn func(*ast.CallExpr) bool
|
||||
current *Block
|
||||
lblocks map[*ast.Object]*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.DeferStmt,
|
||||
*ast.EmptyStmt,
|
||||
*ast.AssignStmt:
|
||||
// No effect on control flow.
|
||||
b.add(s)
|
||||
|
||||
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.newUnreachableBlock("unreachable.call")
|
||||
}
|
||||
|
||||
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)
|
||||
b.jump(label._goto)
|
||||
b.current = label._goto
|
||||
_s = s.Stmt
|
||||
goto start // effectively: tailcall stmt(g, s.Stmt, label)
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
b.add(s)
|
||||
b.current = b.newUnreachableBlock("unreachable.return")
|
||||
|
||||
case *ast.BranchStmt:
|
||||
var block *Block
|
||||
switch s.Tok {
|
||||
case token.BREAK:
|
||||
if s.Label != nil {
|
||||
if lb := b.labeledBlock(s.Label); 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); 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; t = t.tail {
|
||||
block = t._fallthrough
|
||||
}
|
||||
|
||||
case token.GOTO:
|
||||
if s.Label != nil {
|
||||
block = b.labeledBlock(s.Label)._goto
|
||||
}
|
||||
}
|
||||
if block == nil {
|
||||
block = b.newBlock("undefined.branch")
|
||||
}
|
||||
b.jump(block)
|
||||
b.current = b.newUnreachableBlock("unreachable.branch")
|
||||
|
||||
case *ast.BlockStmt:
|
||||
b.stmtList(s.List)
|
||||
|
||||
case *ast.IfStmt:
|
||||
if s.Init != nil {
|
||||
b.stmt(s.Init)
|
||||
}
|
||||
then := b.newBlock("if.then")
|
||||
done := b.newBlock("if.done")
|
||||
_else := done
|
||||
if s.Else != nil {
|
||||
_else = b.newBlock("if.else")
|
||||
}
|
||||
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) 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("switch.done")
|
||||
if label != nil {
|
||||
label._break = done
|
||||
}
|
||||
// We pull the default case (if present) down to the end.
|
||||
// But each fallthrough label must point to the next
|
||||
// body block in source order, so we preallocate a
|
||||
// body block (fallthru) for the next case.
|
||||
// Unfortunately this makes for a confusing block order.
|
||||
var 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("switch.body") // first case only
|
||||
}
|
||||
|
||||
// Preallocate body block for the next case.
|
||||
fallthru = done
|
||||
if i+1 < ncases {
|
||||
fallthru = b.newBlock("switch.body")
|
||||
}
|
||||
|
||||
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("switch.next")
|
||||
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("typeswitch.done")
|
||||
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("typeswitch.body")
|
||||
var next *Block
|
||||
for _, casetype := range cc.List {
|
||||
next = b.newBlock("typeswitch.next")
|
||||
// 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("select.done")
|
||||
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("select.body")
|
||||
next := b.newBlock("select.next")
|
||||
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("for.body")
|
||||
done := b.newBlock("for.done") // target of 'break'
|
||||
loop := body // target of back-edge
|
||||
if s.Cond != nil {
|
||||
loop = b.newBlock("for.loop")
|
||||
}
|
||||
cont := loop // target of 'continue'
|
||||
if s.Post != nil {
|
||||
cont = b.newBlock("for.post")
|
||||
}
|
||||
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("range.loop")
|
||||
b.jump(loop)
|
||||
b.current = loop
|
||||
|
||||
body := b.newBlock("range.body")
|
||||
done := b.newBlock("range.done")
|
||||
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) *lblock {
|
||||
lb := b.lblocks[label.Obj]
|
||||
if lb == nil {
|
||||
lb = &lblock{_goto: b.newBlock(label.Name)}
|
||||
if b.lblocks == nil {
|
||||
b.lblocks = make(map[*ast.Object]*lblock)
|
||||
}
|
||||
b.lblocks[label.Obj] = lb
|
||||
}
|
||||
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(comment string) *Block {
|
||||
g := b.cfg
|
||||
block := &Block{
|
||||
index: int32(len(g.Blocks)),
|
||||
comment: comment,
|
||||
}
|
||||
block.Succs = block.succs2[:0]
|
||||
g.Blocks = append(g.Blocks, block)
|
||||
return block
|
||||
}
|
||||
|
||||
func (b *builder) newUnreachableBlock(comment string) *Block {
|
||||
block := b.newBlock(comment)
|
||||
block.unreachable = true
|
||||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
// 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.
|
||||
|
||||
// This package 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.
|
||||
// For example, this source code:
|
||||
//
|
||||
// if x := f(); x != nil {
|
||||
// T()
|
||||
// } else {
|
||||
// F()
|
||||
// }
|
||||
//
|
||||
// produces this CFG:
|
||||
//
|
||||
// 1: x := f()
|
||||
// x != nil
|
||||
// succs: 2, 3
|
||||
// 2: T()
|
||||
// succs: 4
|
||||
// 3: F()
|
||||
// succs: 4
|
||||
// 4:
|
||||
//
|
||||
// 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
|
||||
|
||||
// Although the vet tool has type information, it is often extremely
|
||||
// fragmentary, so for simplicity this package does not depend on
|
||||
// go/types. Consequently control-flow conditions are ignored even
|
||||
// when constant, and "mayReturn" information must be provided by the
|
||||
// client.
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
type Block struct {
|
||||
Nodes []ast.Node // statements, expressions, and ValueSpecs
|
||||
Succs []*Block // successor nodes in the graph
|
||||
|
||||
comment string // for debugging
|
||||
index int32 // index within CFG.Blocks
|
||||
unreachable bool // is block of stmts following return/panic/for{}
|
||||
succs2 [2]*Block // underlying array for Succs
|
||||
}
|
||||
|
||||
// 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,
|
||||
cfg: new(CFG),
|
||||
}
|
||||
b.current = b.newBlock("entry")
|
||||
b.stmt(body)
|
||||
|
||||
// Does control fall off the end of the function's body?
|
||||
// Make implicit return explicit.
|
||||
if b.current != nil && !b.current.unreachable {
|
||||
b.add(&ast.ReturnStmt{
|
||||
Return: body.End() - 1,
|
||||
})
|
||||
}
|
||||
|
||||
return b.cfg
|
||||
}
|
||||
|
||||
func (b *Block) String() string {
|
||||
return fmt.Sprintf("block %d (%s)", b.index, b.comment)
|
||||
}
|
||||
|
||||
// Return returns the return statement at the end of this block if present, nil otherwise.
|
||||
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)
|
||||
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()
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// 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 whitelist defines exceptions for the vet tool.
|
||||
package whitelist
|
||||
|
||||
// UnkeyedLiteral is a white list of types in the standard packages
|
||||
// that are used with unkeyed literals we deem to be acceptable.
|
||||
var UnkeyedLiteral = map[string]bool{
|
||||
// These image and image/color struct types are frozen. We will never add fields to them.
|
||||
"image/color.Alpha16": true,
|
||||
"image/color.Alpha": true,
|
||||
"image/color.CMYK": true,
|
||||
"image/color.Gray16": true,
|
||||
"image/color.Gray": true,
|
||||
"image/color.NRGBA64": true,
|
||||
"image/color.NRGBA": true,
|
||||
"image/color.NYCbCrA": true,
|
||||
"image/color.RGBA64": true,
|
||||
"image/color.RGBA": true,
|
||||
"image/color.YCbCr": true,
|
||||
"image.Point": true,
|
||||
"image.Rectangle": true,
|
||||
"image.Uniform": true,
|
||||
|
||||
"unicode.Range16": true,
|
||||
}
|
||||
318
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/lostcancel.go
vendored
Normal file
318
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/lostcancel.go
vendored
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
// 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 main
|
||||
|
||||
import (
|
||||
"github.com/dnephin/govet/internal/cfg"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("lostcancel",
|
||||
"check for failure to call cancelation function returned by context.WithCancel",
|
||||
checkLostCancel,
|
||||
funcDecl, funcLit)
|
||||
}
|
||||
|
||||
const debugLostCancel = false
|
||||
|
||||
var contextPackage = "context"
|
||||
|
||||
// checkLostCancel reports a failure to the call the cancel function
|
||||
// returned by context.WithCancel, either because the variable was
|
||||
// assigned to the blank identifier, or because there exists a
|
||||
// control-flow path from the call to a return statement and that path
|
||||
// does not "use" the cancel function. Any reference to the variable
|
||||
// counts as a use, even within a nested function literal.
|
||||
//
|
||||
// checkLostCancel analyzes a single named or literal function.
|
||||
func checkLostCancel(f *File, node ast.Node) {
|
||||
// Fast path: bypass check if file doesn't use context.WithCancel.
|
||||
if !hasImport(f.file, contextPackage) {
|
||||
return
|
||||
}
|
||||
|
||||
// Maps each cancel variable to its defining ValueSpec/AssignStmt.
|
||||
cancelvars := make(map[*types.Var]ast.Node)
|
||||
|
||||
// Find the set of cancel vars to analyze.
|
||||
stack := make([]ast.Node, 0, 32)
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
switch n.(type) {
|
||||
case *ast.FuncLit:
|
||||
if len(stack) > 0 {
|
||||
return false // don't stray into nested functions
|
||||
}
|
||||
case nil:
|
||||
stack = stack[:len(stack)-1] // pop
|
||||
return true
|
||||
}
|
||||
stack = append(stack, n) // push
|
||||
|
||||
// Look for [{AssignStmt,ValueSpec} CallExpr SelectorExpr]:
|
||||
//
|
||||
// ctx, cancel := context.WithCancel(...)
|
||||
// ctx, cancel = context.WithCancel(...)
|
||||
// var ctx, cancel = context.WithCancel(...)
|
||||
//
|
||||
if isContextWithCancel(f, n) && isCall(stack[len(stack)-2]) {
|
||||
var id *ast.Ident // id of cancel var
|
||||
stmt := stack[len(stack)-3]
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.ValueSpec:
|
||||
if len(stmt.Names) > 1 {
|
||||
id = stmt.Names[1]
|
||||
}
|
||||
case *ast.AssignStmt:
|
||||
if len(stmt.Lhs) > 1 {
|
||||
id, _ = stmt.Lhs[1].(*ast.Ident)
|
||||
}
|
||||
}
|
||||
if id != nil {
|
||||
if id.Name == "_" {
|
||||
f.Badf(id.Pos(), "the cancel function returned by context.%s should be called, not discarded, to avoid a context leak",
|
||||
n.(*ast.SelectorExpr).Sel.Name)
|
||||
} else if v, ok := f.pkg.uses[id].(*types.Var); ok {
|
||||
cancelvars[v] = stmt
|
||||
} else if v, ok := f.pkg.defs[id].(*types.Var); ok {
|
||||
cancelvars[v] = stmt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if len(cancelvars) == 0 {
|
||||
return // no need to build CFG
|
||||
}
|
||||
|
||||
// Tell the CFG builder which functions never return.
|
||||
info := &types.Info{Uses: f.pkg.uses, Selections: f.pkg.selectors}
|
||||
mayReturn := func(call *ast.CallExpr) bool {
|
||||
name := callName(info, call)
|
||||
return !noReturnFuncs[name]
|
||||
}
|
||||
|
||||
// Build the CFG.
|
||||
var g *cfg.CFG
|
||||
var sig *types.Signature
|
||||
switch node := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
sig, _ = f.pkg.defs[node.Name].Type().(*types.Signature)
|
||||
g = cfg.New(node.Body, mayReturn)
|
||||
case *ast.FuncLit:
|
||||
sig, _ = f.pkg.types[node.Type].Type.(*types.Signature)
|
||||
g = cfg.New(node.Body, mayReturn)
|
||||
}
|
||||
|
||||
// Print CFG.
|
||||
if debugLostCancel {
|
||||
fmt.Println(g.Format(f.fset))
|
||||
}
|
||||
|
||||
// Examine the CFG for each variable in turn.
|
||||
// (It would be more efficient to analyze all cancelvars in a
|
||||
// single pass over the AST, but seldom is there more than one.)
|
||||
for v, stmt := range cancelvars {
|
||||
if ret := lostCancelPath(f, g, v, stmt, sig); ret != nil {
|
||||
lineno := f.fset.Position(stmt.Pos()).Line
|
||||
f.Badf(stmt.Pos(), "the %s function is not used on all paths (possible context leak)", v.Name())
|
||||
f.Badf(ret.Pos(), "this return statement may be reached without using the %s var defined on line %d", v.Name(), lineno)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isCall(n ast.Node) bool { _, ok := n.(*ast.CallExpr); return ok }
|
||||
|
||||
func hasImport(f *ast.File, path string) bool {
|
||||
for _, imp := range f.Imports {
|
||||
v, _ := strconv.Unquote(imp.Path.Value)
|
||||
if v == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isContextWithCancel reports whether n is one of the qualified identifiers
|
||||
// context.With{Cancel,Timeout,Deadline}.
|
||||
func isContextWithCancel(f *File, n ast.Node) bool {
|
||||
if sel, ok := n.(*ast.SelectorExpr); ok {
|
||||
switch sel.Sel.Name {
|
||||
case "WithCancel", "WithTimeout", "WithDeadline":
|
||||
if x, ok := sel.X.(*ast.Ident); ok {
|
||||
if pkgname, ok := f.pkg.uses[x].(*types.PkgName); ok {
|
||||
return pkgname.Imported().Path() == contextPackage
|
||||
}
|
||||
// Import failed, so we can't check package path.
|
||||
// Just check the local package name (heuristic).
|
||||
return x.Name == "context"
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// lostCancelPath finds a path through the CFG, from stmt (which defines
|
||||
// the 'cancel' variable v) to a return statement, that doesn't "use" v.
|
||||
// If it finds one, it returns the return statement (which may be synthetic).
|
||||
// sig is the function's type, if known.
|
||||
func lostCancelPath(f *File, g *cfg.CFG, v *types.Var, stmt ast.Node, sig *types.Signature) *ast.ReturnStmt {
|
||||
vIsNamedResult := sig != nil && tupleContains(sig.Results(), v)
|
||||
|
||||
// uses reports whether stmts contain a "use" of variable v.
|
||||
uses := func(f *File, v *types.Var, stmts []ast.Node) bool {
|
||||
found := false
|
||||
for _, stmt := range stmts {
|
||||
ast.Inspect(stmt, func(n ast.Node) bool {
|
||||
switch n := n.(type) {
|
||||
case *ast.Ident:
|
||||
if f.pkg.uses[n] == v {
|
||||
found = true
|
||||
}
|
||||
case *ast.ReturnStmt:
|
||||
// A naked return statement counts as a use
|
||||
// of the named result variables.
|
||||
if n.Results == nil && vIsNamedResult {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return !found
|
||||
})
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
// blockUses computes "uses" for each block, caching the result.
|
||||
memo := make(map[*cfg.Block]bool)
|
||||
blockUses := func(f *File, v *types.Var, b *cfg.Block) bool {
|
||||
res, ok := memo[b]
|
||||
if !ok {
|
||||
res = uses(f, v, b.Nodes)
|
||||
memo[b] = res
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Find the var's defining block in the CFG,
|
||||
// plus the rest of the statements of that block.
|
||||
var defblock *cfg.Block
|
||||
var rest []ast.Node
|
||||
outer:
|
||||
for _, b := range g.Blocks {
|
||||
for i, n := range b.Nodes {
|
||||
if n == stmt {
|
||||
defblock = b
|
||||
rest = b.Nodes[i+1:]
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
if defblock == nil {
|
||||
panic("internal error: can't find defining block for cancel var")
|
||||
}
|
||||
|
||||
// Is v "used" in the remainder of its defining block?
|
||||
if uses(f, v, rest) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Does the defining block return without using v?
|
||||
if ret := defblock.Return(); ret != nil {
|
||||
return ret
|
||||
}
|
||||
|
||||
// Search the CFG depth-first for a path, from defblock to a
|
||||
// return block, in which v is never "used".
|
||||
seen := make(map[*cfg.Block]bool)
|
||||
var search func(blocks []*cfg.Block) *ast.ReturnStmt
|
||||
search = func(blocks []*cfg.Block) *ast.ReturnStmt {
|
||||
for _, b := range blocks {
|
||||
if !seen[b] {
|
||||
seen[b] = true
|
||||
|
||||
// Prune the search if the block uses v.
|
||||
if blockUses(f, v, b) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Found path to return statement?
|
||||
if ret := b.Return(); ret != nil {
|
||||
if debugLostCancel {
|
||||
fmt.Printf("found path to return in block %s\n", b)
|
||||
}
|
||||
return ret // found
|
||||
}
|
||||
|
||||
// Recur
|
||||
if ret := search(b.Succs); ret != nil {
|
||||
if debugLostCancel {
|
||||
fmt.Printf(" from block %s\n", b)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return search(defblock.Succs)
|
||||
}
|
||||
|
||||
func tupleContains(tuple *types.Tuple, v *types.Var) bool {
|
||||
for i := 0; i < tuple.Len(); i++ {
|
||||
if tuple.At(i) == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var noReturnFuncs = map[string]bool{
|
||||
"(*testing.common).FailNow": true,
|
||||
"(*testing.common).Fatal": true,
|
||||
"(*testing.common).Fatalf": true,
|
||||
"(*testing.common).Skip": true,
|
||||
"(*testing.common).SkipNow": true,
|
||||
"(*testing.common).Skipf": true,
|
||||
"log.Fatal": true,
|
||||
"log.Fatalf": true,
|
||||
"log.Fatalln": true,
|
||||
"os.Exit": true,
|
||||
"panic": true,
|
||||
"runtime.Goexit": true,
|
||||
}
|
||||
|
||||
// callName returns the canonical name of the builtin, method, or
|
||||
// function called by call, if known.
|
||||
func callName(info *types.Info, call *ast.CallExpr) string {
|
||||
switch fun := call.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
// builtin, e.g. "panic"
|
||||
if obj, ok := info.Uses[fun].(*types.Builtin); ok {
|
||||
return obj.Name()
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
if sel, ok := info.Selections[fun]; ok && sel.Kind() == types.MethodVal {
|
||||
// method call, e.g. "(*testing.common).Fatal"
|
||||
meth := sel.Obj()
|
||||
return fmt.Sprintf("(%s).%s",
|
||||
meth.Type().(*types.Signature).Recv().Type(),
|
||||
meth.Name())
|
||||
}
|
||||
if obj, ok := info.Uses[fun.Sel]; ok {
|
||||
// qualified identifier, e.g. "os.Exit"
|
||||
return fmt.Sprintf("%s.%s",
|
||||
obj.Pkg().Path(),
|
||||
obj.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// function with no name, or defined in missing imported package
|
||||
return ""
|
||||
}
|
||||
504
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/main.go
vendored
Normal file
504
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/main.go
vendored
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Vet is a simple checker for static errors in Go source code.
|
||||
// See doc.go for more information.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
verbose = flag.Bool("v", false, "verbose")
|
||||
tags = flag.String("tags", "", "comma-separated list of build tags to apply when parsing")
|
||||
noRecurse = flag.Bool("no-recurse", false, "disable recursive directory walking")
|
||||
tagList = []string{} // exploded version of tags flag; set in main
|
||||
)
|
||||
|
||||
var exitCode = 0
|
||||
|
||||
// "-all" flag enables all non-experimental checks
|
||||
var all = triStateFlag("all", unset, "enable all non-experimental checks")
|
||||
|
||||
// Flags to control which individual checks to perform.
|
||||
var report = map[string]*triState{
|
||||
// Only unusual checks are written here.
|
||||
// Most checks that operate during the AST walk are added by register.
|
||||
"asmdecl": triStateFlag("asmdecl", unset, "check assembly against Go declarations"),
|
||||
"buildtags": triStateFlag("buildtags", unset, "check that +build tags are valid"),
|
||||
}
|
||||
|
||||
// experimental records the flags enabling experimental features. These must be
|
||||
// requested explicitly; they are not enabled by -all.
|
||||
var experimental = map[string]bool{}
|
||||
|
||||
// setTrueCount record how many flags are explicitly set to true.
|
||||
var setTrueCount int
|
||||
|
||||
// dirsRun and filesRun indicate whether the vet is applied to directory or
|
||||
// file targets. The distinction affects which checks are run.
|
||||
var dirsRun, filesRun bool
|
||||
|
||||
// includesNonTest indicates whether the vet is applied to non-test targets.
|
||||
// Certain checks are relevant only if they touch both test and non-test files.
|
||||
var includesNonTest bool
|
||||
|
||||
// A triState is a boolean that knows whether it has been set to either true or false.
|
||||
// It is used to identify if a flag appears; the standard boolean flag cannot
|
||||
// distinguish missing from unset. It also satisfies flag.Value.
|
||||
type triState int
|
||||
|
||||
const (
|
||||
unset triState = iota
|
||||
setTrue
|
||||
setFalse
|
||||
)
|
||||
|
||||
func triStateFlag(name string, value triState, usage string) *triState {
|
||||
flag.Var(&value, name, usage)
|
||||
return &value
|
||||
}
|
||||
|
||||
// triState implements flag.Value, flag.Getter, and flag.boolFlag.
|
||||
// They work like boolean flags: we can say vet -printf as well as vet -printf=true
|
||||
func (ts *triState) Get() interface{} {
|
||||
return *ts == setTrue
|
||||
}
|
||||
|
||||
func (ts triState) isTrue() bool {
|
||||
return ts == setTrue
|
||||
}
|
||||
|
||||
func (ts *triState) Set(value string) error {
|
||||
b, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if b {
|
||||
*ts = setTrue
|
||||
setTrueCount++
|
||||
} else {
|
||||
*ts = setFalse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *triState) String() string {
|
||||
switch *ts {
|
||||
case unset:
|
||||
return "true" // An unset flag will be set by -all, so defaults to true.
|
||||
case setTrue:
|
||||
return "true"
|
||||
case setFalse:
|
||||
return "false"
|
||||
}
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (ts triState) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// vet tells whether to report errors for the named check, a flag name.
|
||||
func vet(name string) bool {
|
||||
return report[name].isTrue()
|
||||
}
|
||||
|
||||
// setExit sets the value for os.Exit when it is called, later. It
|
||||
// remembers the highest value.
|
||||
func setExit(err int) {
|
||||
if err > exitCode {
|
||||
exitCode = err
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// Each of these vars has a corresponding case in (*File).Visit.
|
||||
assignStmt *ast.AssignStmt
|
||||
binaryExpr *ast.BinaryExpr
|
||||
callExpr *ast.CallExpr
|
||||
compositeLit *ast.CompositeLit
|
||||
exprStmt *ast.ExprStmt
|
||||
field *ast.Field
|
||||
funcDecl *ast.FuncDecl
|
||||
funcLit *ast.FuncLit
|
||||
genDecl *ast.GenDecl
|
||||
interfaceType *ast.InterfaceType
|
||||
rangeStmt *ast.RangeStmt
|
||||
returnStmt *ast.ReturnStmt
|
||||
|
||||
// checkers is a two-level map.
|
||||
// The outer level is keyed by a nil pointer, one of the AST vars above.
|
||||
// The inner level is keyed by checker name.
|
||||
checkers = make(map[ast.Node]map[string]func(*File, ast.Node))
|
||||
)
|
||||
|
||||
func register(name, usage string, fn func(*File, ast.Node), types ...ast.Node) {
|
||||
report[name] = triStateFlag(name, unset, usage)
|
||||
for _, typ := range types {
|
||||
m := checkers[typ]
|
||||
if m == nil {
|
||||
m = make(map[string]func(*File, ast.Node))
|
||||
checkers[typ] = m
|
||||
}
|
||||
m[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// Usage is a replacement usage function for the flags package.
|
||||
func Usage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n")
|
||||
fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n")
|
||||
fmt.Fprintf(os.Stderr, "By default, -all is set and all non-experimental checks are run.\n")
|
||||
fmt.Fprintf(os.Stderr, "For more information run\n")
|
||||
fmt.Fprintf(os.Stderr, "\tgo doc cmd/vet\n\n")
|
||||
fmt.Fprintf(os.Stderr, "Flags:\n")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// File is a wrapper for the state of a file used in the parser.
|
||||
// The parse tree walkers are all methods of this type.
|
||||
type File struct {
|
||||
pkg *Package
|
||||
fset *token.FileSet
|
||||
name string
|
||||
content []byte
|
||||
file *ast.File
|
||||
b bytes.Buffer // for use by methods
|
||||
|
||||
// Parsed package "foo" when checking package "foo_test"
|
||||
basePkg *Package
|
||||
|
||||
// The objects that are receivers of a "String() string" method.
|
||||
// This is used by the recursiveStringer method in print.go.
|
||||
stringers map[*ast.Object]bool
|
||||
|
||||
// Registered checkers to run.
|
||||
checkers map[ast.Node][]func(*File, ast.Node)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = Usage
|
||||
flag.Parse()
|
||||
|
||||
// If any flag is set, we run only those checks requested.
|
||||
// If all flag is set true or if no flags are set true, set all the non-experimental ones
|
||||
// not explicitly set (in effect, set the "-all" flag).
|
||||
if setTrueCount == 0 || *all == setTrue {
|
||||
for name, setting := range report {
|
||||
if *setting == unset && !experimental[name] {
|
||||
*setting = setTrue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tagList = strings.Split(*tags, ",")
|
||||
|
||||
initPrintFlags()
|
||||
initUnusedFlags()
|
||||
|
||||
if flag.NArg() == 0 {
|
||||
Usage()
|
||||
}
|
||||
for _, name := range flag.Args() {
|
||||
// Is it a directory?
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
warnf("error walking tree: %s", err)
|
||||
continue
|
||||
}
|
||||
if fi.IsDir() {
|
||||
dirsRun = true
|
||||
} else {
|
||||
filesRun = true
|
||||
if !strings.HasSuffix(name, "_test.go") {
|
||||
includesNonTest = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if dirsRun && filesRun {
|
||||
Usage()
|
||||
}
|
||||
if dirsRun {
|
||||
for _, name := range flag.Args() {
|
||||
if *noRecurse {
|
||||
doPackageDir(name)
|
||||
} else {
|
||||
walkDir(name)
|
||||
}
|
||||
}
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
if doPackage(".", flag.Args(), nil) == nil {
|
||||
warnf("no files checked")
|
||||
}
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
// prefixDirectory places the directory name on the beginning of each name in the list.
|
||||
func prefixDirectory(directory string, names []string) {
|
||||
if directory != "." {
|
||||
for i, name := range names {
|
||||
names[i] = filepath.Join(directory, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// doPackageDir analyzes the single package found in the directory, if there is one,
|
||||
// plus a test package, if there is one.
|
||||
func doPackageDir(directory string) {
|
||||
context := build.Default
|
||||
if len(context.BuildTags) != 0 {
|
||||
warnf("build tags %s previously set", context.BuildTags)
|
||||
}
|
||||
context.BuildTags = append(tagList, context.BuildTags...)
|
||||
|
||||
pkg, err := context.ImportDir(directory, 0)
|
||||
if err != nil {
|
||||
// If it's just that there are no go source files, that's fine.
|
||||
if _, nogo := err.(*build.NoGoError); nogo {
|
||||
return
|
||||
}
|
||||
// Non-fatal: we are doing a recursive walk and there may be other directories.
|
||||
warnf("cannot process directory %s: %s", directory, err)
|
||||
return
|
||||
}
|
||||
var names []string
|
||||
names = append(names, pkg.GoFiles...)
|
||||
names = append(names, pkg.CgoFiles...)
|
||||
names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
|
||||
names = append(names, pkg.SFiles...)
|
||||
prefixDirectory(directory, names)
|
||||
basePkg := doPackage(directory, names, nil)
|
||||
// Is there also a "foo_test" package? If so, do that one as well.
|
||||
if len(pkg.XTestGoFiles) > 0 {
|
||||
names = pkg.XTestGoFiles
|
||||
prefixDirectory(directory, names)
|
||||
doPackage(directory, names, basePkg)
|
||||
}
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
path string
|
||||
defs map[*ast.Ident]types.Object
|
||||
uses map[*ast.Ident]types.Object
|
||||
selectors map[*ast.SelectorExpr]*types.Selection
|
||||
types map[ast.Expr]types.TypeAndValue
|
||||
spans map[types.Object]Span
|
||||
files []*File
|
||||
typesPkg *types.Package
|
||||
}
|
||||
|
||||
// doPackage analyzes the single package constructed from the named files.
|
||||
// It returns the parsed Package or nil if none of the files have been checked.
|
||||
func doPackage(directory string, names []string, basePkg *Package) *Package {
|
||||
var files []*File
|
||||
var astFiles []*ast.File
|
||||
fs := token.NewFileSet()
|
||||
for _, name := range names {
|
||||
data, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
// Warn but continue to next package.
|
||||
warnf("%s: %s", name, err)
|
||||
return nil
|
||||
}
|
||||
checkBuildTag(name, data)
|
||||
var parsedFile *ast.File
|
||||
if strings.HasSuffix(name, ".go") {
|
||||
parsedFile, err = parser.ParseFile(fs, name, data, 0)
|
||||
if err != nil {
|
||||
warnf("%s: %s", name, err)
|
||||
return nil
|
||||
}
|
||||
astFiles = append(astFiles, parsedFile)
|
||||
}
|
||||
files = append(files, &File{fset: fs, content: data, name: name, file: parsedFile})
|
||||
}
|
||||
if len(astFiles) == 0 {
|
||||
return nil
|
||||
}
|
||||
pkg := new(Package)
|
||||
pkg.path = astFiles[0].Name.Name
|
||||
pkg.files = files
|
||||
// Type check the package.
|
||||
err := pkg.check(fs, astFiles)
|
||||
if err != nil && *verbose {
|
||||
warnf("%s", err)
|
||||
}
|
||||
|
||||
// Check.
|
||||
chk := make(map[ast.Node][]func(*File, ast.Node))
|
||||
for typ, set := range checkers {
|
||||
for name, fn := range set {
|
||||
if vet(name) {
|
||||
chk[typ] = append(chk[typ], fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, file := range files {
|
||||
file.pkg = pkg
|
||||
file.basePkg = basePkg
|
||||
file.checkers = chk
|
||||
if file.file != nil {
|
||||
file.walkFile(file.name, file.file)
|
||||
}
|
||||
}
|
||||
asmCheck(pkg)
|
||||
return pkg
|
||||
}
|
||||
|
||||
func visit(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
warnf("walk error: %s", err)
|
||||
return err
|
||||
}
|
||||
// One package per directory. Ignore the files themselves.
|
||||
if !f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
doPackageDir(path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pkg *Package) hasFileWithSuffix(suffix string) bool {
|
||||
for _, f := range pkg.files {
|
||||
if strings.HasSuffix(f.name, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// walkDir recursively walks the tree looking for Go packages.
|
||||
func walkDir(root string) {
|
||||
filepath.Walk(root, visit)
|
||||
}
|
||||
|
||||
// errorf formats the error to standard error, adding program
|
||||
// identification and a newline, and exits.
|
||||
func errorf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// warnf formats the error to standard error, adding program
|
||||
// identification and a newline, but does not exit.
|
||||
func warnf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
|
||||
setExit(1)
|
||||
}
|
||||
|
||||
// Println is fmt.Println guarded by -v.
|
||||
func Println(args ...interface{}) {
|
||||
if !*verbose {
|
||||
return
|
||||
}
|
||||
fmt.Println(args...)
|
||||
}
|
||||
|
||||
// Printf is fmt.Printf guarded by -v.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
if !*verbose {
|
||||
return
|
||||
}
|
||||
fmt.Printf(format+"\n", args...)
|
||||
}
|
||||
|
||||
// Bad reports an error and sets the exit code..
|
||||
func (f *File) Bad(pos token.Pos, args ...interface{}) {
|
||||
f.Warn(pos, args...)
|
||||
setExit(1)
|
||||
}
|
||||
|
||||
// Badf reports a formatted error and sets the exit code.
|
||||
func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
|
||||
f.Warnf(pos, format, args...)
|
||||
setExit(1)
|
||||
}
|
||||
|
||||
// loc returns a formatted representation of the position.
|
||||
func (f *File) loc(pos token.Pos) string {
|
||||
if pos == token.NoPos {
|
||||
return ""
|
||||
}
|
||||
// Do not print columns. Because the pos often points to the start of an
|
||||
// expression instead of the inner part with the actual error, the
|
||||
// precision can mislead.
|
||||
posn := f.fset.Position(pos)
|
||||
return fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
|
||||
}
|
||||
|
||||
// Warn reports an error but does not set the exit code.
|
||||
func (f *File) Warn(pos token.Pos, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s", f.loc(pos), fmt.Sprintln(args...))
|
||||
}
|
||||
|
||||
// Warnf reports a formatted error but does not set the exit code.
|
||||
func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", f.loc(pos), fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// walkFile walks the file's tree.
|
||||
func (f *File) walkFile(name string, file *ast.File) {
|
||||
Println("Checking file", name)
|
||||
ast.Walk(f, file)
|
||||
}
|
||||
|
||||
// Visit implements the ast.Visitor interface.
|
||||
func (f *File) Visit(node ast.Node) ast.Visitor {
|
||||
var key ast.Node
|
||||
switch node.(type) {
|
||||
case *ast.AssignStmt:
|
||||
key = assignStmt
|
||||
case *ast.BinaryExpr:
|
||||
key = binaryExpr
|
||||
case *ast.CallExpr:
|
||||
key = callExpr
|
||||
case *ast.CompositeLit:
|
||||
key = compositeLit
|
||||
case *ast.ExprStmt:
|
||||
key = exprStmt
|
||||
case *ast.Field:
|
||||
key = field
|
||||
case *ast.FuncDecl:
|
||||
key = funcDecl
|
||||
case *ast.FuncLit:
|
||||
key = funcLit
|
||||
case *ast.GenDecl:
|
||||
key = genDecl
|
||||
case *ast.InterfaceType:
|
||||
key = interfaceType
|
||||
case *ast.RangeStmt:
|
||||
key = rangeStmt
|
||||
case *ast.ReturnStmt:
|
||||
key = returnStmt
|
||||
}
|
||||
for _, fn := range f.checkers[key] {
|
||||
fn(f, node)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// gofmt returns a string representation of the expression.
|
||||
func (f *File) gofmt(x ast.Expr) string {
|
||||
f.b.Reset()
|
||||
printer.Fprint(&f.b, f.fset, x)
|
||||
return f.b.String()
|
||||
}
|
||||
182
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/method.go
vendored
Normal file
182
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/method.go
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains the code to check canonical methods.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/printer"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("methods",
|
||||
"check that canonically named methods are canonically defined",
|
||||
checkCanonicalMethod,
|
||||
funcDecl, interfaceType)
|
||||
}
|
||||
|
||||
type MethodSig struct {
|
||||
args []string
|
||||
results []string
|
||||
}
|
||||
|
||||
// canonicalMethods lists the input and output types for Go methods
|
||||
// that are checked using dynamic interface checks. Because the
|
||||
// checks are dynamic, such methods would not cause a compile error
|
||||
// if they have the wrong signature: instead the dynamic check would
|
||||
// fail, sometimes mysteriously. If a method is found with a name listed
|
||||
// here but not the input/output types listed here, vet complains.
|
||||
//
|
||||
// A few of the canonical methods have very common names.
|
||||
// For example, a type might implement a Scan method that
|
||||
// has nothing to do with fmt.Scanner, but we still want to check
|
||||
// the methods that are intended to implement fmt.Scanner.
|
||||
// To do that, the arguments that have a = prefix are treated as
|
||||
// signals that the canonical meaning is intended: if a Scan
|
||||
// method doesn't have a fmt.ScanState as its first argument,
|
||||
// we let it go. But if it does have a fmt.ScanState, then the
|
||||
// rest has to match.
|
||||
var canonicalMethods = map[string]MethodSig{
|
||||
// "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict
|
||||
"Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter
|
||||
"GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder
|
||||
"GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder
|
||||
"MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler
|
||||
"MarshalXML": {[]string{"*xml.Encoder", "xml.StartElement"}, []string{"error"}}, // xml.Marshaler
|
||||
"Peek": {[]string{"=int"}, []string{"[]byte", "error"}}, // image.reader (matching bufio.Reader)
|
||||
"ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader
|
||||
"ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom
|
||||
"ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader
|
||||
"Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner
|
||||
"Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker
|
||||
"UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler
|
||||
"UnmarshalXML": {[]string{"*xml.Decoder", "xml.StartElement"}, []string{"error"}}, // xml.Unmarshaler
|
||||
"UnreadByte": {[]string{}, []string{"error"}},
|
||||
"UnreadRune": {[]string{}, []string{"error"}},
|
||||
"WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer)
|
||||
"WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo
|
||||
}
|
||||
|
||||
func checkCanonicalMethod(f *File, node ast.Node) {
|
||||
switch n := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if n.Recv != nil {
|
||||
canonicalMethod(f, n.Name, n.Type)
|
||||
}
|
||||
case *ast.InterfaceType:
|
||||
for _, field := range n.Methods.List {
|
||||
for _, id := range field.Names {
|
||||
canonicalMethod(f, id, field.Type.(*ast.FuncType))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func canonicalMethod(f *File, id *ast.Ident, t *ast.FuncType) {
|
||||
// Expected input/output.
|
||||
expect, ok := canonicalMethods[id.Name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Actual input/output
|
||||
args := typeFlatten(t.Params.List)
|
||||
var results []ast.Expr
|
||||
if t.Results != nil {
|
||||
results = typeFlatten(t.Results.List)
|
||||
}
|
||||
|
||||
// Do the =s (if any) all match?
|
||||
if !f.matchParams(expect.args, args, "=") || !f.matchParams(expect.results, results, "=") {
|
||||
return
|
||||
}
|
||||
|
||||
// Everything must match.
|
||||
if !f.matchParams(expect.args, args, "") || !f.matchParams(expect.results, results, "") {
|
||||
expectFmt := id.Name + "(" + argjoin(expect.args) + ")"
|
||||
if len(expect.results) == 1 {
|
||||
expectFmt += " " + argjoin(expect.results)
|
||||
} else if len(expect.results) > 1 {
|
||||
expectFmt += " (" + argjoin(expect.results) + ")"
|
||||
}
|
||||
|
||||
f.b.Reset()
|
||||
if err := printer.Fprint(&f.b, f.fset, t); err != nil {
|
||||
fmt.Fprintf(&f.b, "<%s>", err)
|
||||
}
|
||||
actual := f.b.String()
|
||||
actual = strings.TrimPrefix(actual, "func")
|
||||
actual = id.Name + actual
|
||||
|
||||
f.Badf(id.Pos(), "method %s should have signature %s", actual, expectFmt)
|
||||
}
|
||||
}
|
||||
|
||||
func argjoin(x []string) string {
|
||||
y := make([]string, len(x))
|
||||
for i, s := range x {
|
||||
if s[0] == '=' {
|
||||
s = s[1:]
|
||||
}
|
||||
y[i] = s
|
||||
}
|
||||
return strings.Join(y, ", ")
|
||||
}
|
||||
|
||||
// Turn parameter list into slice of types
|
||||
// (in the ast, types are Exprs).
|
||||
// Have to handle f(int, bool) and f(x, y, z int)
|
||||
// so not a simple 1-to-1 conversion.
|
||||
func typeFlatten(l []*ast.Field) []ast.Expr {
|
||||
var t []ast.Expr
|
||||
for _, f := range l {
|
||||
if len(f.Names) == 0 {
|
||||
t = append(t, f.Type)
|
||||
continue
|
||||
}
|
||||
for _ = range f.Names {
|
||||
t = append(t, f.Type)
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Does each type in expect with the given prefix match the corresponding type in actual?
|
||||
func (f *File) matchParams(expect []string, actual []ast.Expr, prefix string) bool {
|
||||
for i, x := range expect {
|
||||
if !strings.HasPrefix(x, prefix) {
|
||||
continue
|
||||
}
|
||||
if i >= len(actual) {
|
||||
return false
|
||||
}
|
||||
if !f.matchParamType(x, actual[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if prefix == "" && len(actual) > len(expect) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Does this one type match?
|
||||
func (f *File) matchParamType(expect string, actual ast.Expr) bool {
|
||||
if strings.HasPrefix(expect, "=") {
|
||||
expect = expect[1:]
|
||||
}
|
||||
// Strip package name if we're in that package.
|
||||
if n := len(f.file.Name.Name); len(expect) > n && expect[:n] == f.file.Name.Name && expect[n] == '.' {
|
||||
expect = expect[n+1:]
|
||||
}
|
||||
|
||||
// Overkill but easy.
|
||||
f.b.Reset()
|
||||
printer.Fprint(&f.b, f.fset, actual)
|
||||
return f.b.String() == expect
|
||||
}
|
||||
67
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/nilfunc.go
vendored
Normal file
67
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/nilfunc.go
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// 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.
|
||||
|
||||
/*
|
||||
This file contains the code to check for useless function comparisons.
|
||||
A useless comparison is one like f == nil as opposed to f() == nil.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("nilfunc",
|
||||
"check for comparisons between functions and nil",
|
||||
checkNilFuncComparison,
|
||||
binaryExpr)
|
||||
}
|
||||
|
||||
func checkNilFuncComparison(f *File, node ast.Node) {
|
||||
e := node.(*ast.BinaryExpr)
|
||||
|
||||
// Only want == or != comparisons.
|
||||
if e.Op != token.EQL && e.Op != token.NEQ {
|
||||
return
|
||||
}
|
||||
|
||||
// Only want comparisons with a nil identifier on one side.
|
||||
var e2 ast.Expr
|
||||
switch {
|
||||
case f.isNil(e.X):
|
||||
e2 = e.Y
|
||||
case f.isNil(e.Y):
|
||||
e2 = e.X
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
// Only want identifiers or selector expressions.
|
||||
var obj types.Object
|
||||
switch v := e2.(type) {
|
||||
case *ast.Ident:
|
||||
obj = f.pkg.uses[v]
|
||||
case *ast.SelectorExpr:
|
||||
obj = f.pkg.uses[v.Sel]
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
// Only want functions.
|
||||
if _, ok := obj.(*types.Func); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
f.Badf(e.Pos(), "comparison of function %v %v nil is always %v", obj.Name(), e.Op, e.Op == token.NEQ)
|
||||
}
|
||||
|
||||
// isNil reports whether the provided expression is the built-in nil
|
||||
// identifier.
|
||||
func (f *File) isNil(e ast.Expr) bool {
|
||||
return f.pkg.types[e].Type == types.Typ[types.UntypedNil]
|
||||
}
|
||||
650
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/print.go
vendored
Normal file
650
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/print.go
vendored
Normal file
|
|
@ -0,0 +1,650 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains the printf-checker.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check")
|
||||
|
||||
func init() {
|
||||
register("printf",
|
||||
"check printf-like invocations",
|
||||
checkFmtPrintfCall,
|
||||
funcDecl, callExpr)
|
||||
}
|
||||
|
||||
func initPrintFlags() {
|
||||
if *printfuncs == "" {
|
||||
return
|
||||
}
|
||||
for _, name := range strings.Split(*printfuncs, ",") {
|
||||
if len(name) == 0 {
|
||||
flag.Usage()
|
||||
}
|
||||
|
||||
// Backwards compatibility: skip optional first argument
|
||||
// index after the colon.
|
||||
if colon := strings.LastIndex(name, ":"); colon > 0 {
|
||||
name = name[:colon]
|
||||
}
|
||||
|
||||
name = strings.ToLower(name)
|
||||
if name[len(name)-1] == 'f' {
|
||||
isFormattedPrint[name] = true
|
||||
} else {
|
||||
isPrint[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isFormattedPrint records the formatted-print functions. Names are
|
||||
// lower-cased so the lookup is case insensitive.
|
||||
var isFormattedPrint = map[string]bool{
|
||||
"errorf": true,
|
||||
"fatalf": true,
|
||||
"fprintf": true,
|
||||
"logf": true,
|
||||
"panicf": true,
|
||||
"printf": true,
|
||||
"sprintf": true,
|
||||
}
|
||||
|
||||
// isPrint records the unformatted-print functions. Names are lower-cased
|
||||
// so the lookup is case insensitive.
|
||||
var isPrint = map[string]bool{
|
||||
"error": true,
|
||||
"fatal": true,
|
||||
"fprint": true,
|
||||
"fprintln": true,
|
||||
"log": true,
|
||||
"panic": true,
|
||||
"panicln": true,
|
||||
"print": true,
|
||||
"println": true,
|
||||
"sprint": true,
|
||||
"sprintln": true,
|
||||
}
|
||||
|
||||
// formatString returns the format string argument and its index within
|
||||
// the given printf-like call expression.
|
||||
//
|
||||
// The last parameter before variadic arguments is assumed to be
|
||||
// a format string.
|
||||
//
|
||||
// The first string literal or string constant is assumed to be a format string
|
||||
// if the call's signature cannot be determined.
|
||||
//
|
||||
// If it cannot find any format string parameter, it returns ("", -1).
|
||||
func formatString(f *File, call *ast.CallExpr) (string, int) {
|
||||
typ := f.pkg.types[call.Fun].Type
|
||||
if typ != nil {
|
||||
if sig, ok := typ.(*types.Signature); ok {
|
||||
if !sig.Variadic() {
|
||||
// Skip checking non-variadic functions
|
||||
return "", -1
|
||||
}
|
||||
idx := sig.Params().Len() - 2
|
||||
if idx < 0 {
|
||||
// Skip checking variadic functions without
|
||||
// fixed arguments.
|
||||
return "", -1
|
||||
}
|
||||
s, ok := stringLiteralArg(f, call, idx)
|
||||
if !ok {
|
||||
// The last argument before variadic args isn't a string
|
||||
return "", -1
|
||||
}
|
||||
return s, idx
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot determine call's signature. Fallback to scanning for the first
|
||||
// string argument in the call
|
||||
for idx := range call.Args {
|
||||
if s, ok := stringLiteralArg(f, call, idx); ok {
|
||||
return s, idx
|
||||
}
|
||||
}
|
||||
return "", -1
|
||||
}
|
||||
|
||||
// stringLiteralArg returns call's string constant argument at the index idx.
|
||||
//
|
||||
// ("", false) is returned if call's argument at the index idx isn't a string
|
||||
// literal.
|
||||
func stringLiteralArg(f *File, call *ast.CallExpr, idx int) (string, bool) {
|
||||
if idx >= len(call.Args) {
|
||||
return "", false
|
||||
}
|
||||
arg := call.Args[idx]
|
||||
lit := f.pkg.types[arg].Value
|
||||
if lit != nil && lit.Kind() == constant.String {
|
||||
return constant.StringVal(lit), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// checkCall triggers the print-specific checks if the call invokes a print function.
|
||||
func checkFmtPrintfCall(f *File, node ast.Node) {
|
||||
if d, ok := node.(*ast.FuncDecl); ok && isStringer(f, d) {
|
||||
// Remember we saw this.
|
||||
if f.stringers == nil {
|
||||
f.stringers = make(map[*ast.Object]bool)
|
||||
}
|
||||
if l := d.Recv.List; len(l) == 1 {
|
||||
if n := l[0].Names; len(n) == 1 {
|
||||
f.stringers[n[0].Obj] = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var Name string
|
||||
switch x := call.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
Name = x.Name
|
||||
case *ast.SelectorExpr:
|
||||
Name = x.Sel.Name
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
name := strings.ToLower(Name)
|
||||
if _, ok := isFormattedPrint[name]; ok {
|
||||
f.checkPrintf(call, Name)
|
||||
return
|
||||
}
|
||||
if _, ok := isPrint[name]; ok {
|
||||
f.checkPrint(call, Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// isStringer returns true if the provided declaration is a "String() string"
|
||||
// method, an implementation of fmt.Stringer.
|
||||
func isStringer(f *File, d *ast.FuncDecl) bool {
|
||||
return d.Recv != nil && d.Name.Name == "String" && d.Type.Results != nil &&
|
||||
len(d.Type.Params.List) == 0 && len(d.Type.Results.List) == 1 &&
|
||||
f.pkg.types[d.Type.Results.List[0].Type].Type == types.Typ[types.String]
|
||||
}
|
||||
|
||||
// formatState holds the parsed representation of a printf directive such as "%3.*[4]d".
|
||||
// It is constructed by parsePrintfVerb.
|
||||
type formatState struct {
|
||||
verb rune // the format verb: 'd' for "%d"
|
||||
format string // the full format directive from % through verb, "%.3d".
|
||||
name string // Printf, Sprintf etc.
|
||||
flags []byte // the list of # + etc.
|
||||
argNums []int // the successive argument numbers that are consumed, adjusted to refer to actual arg in call
|
||||
indexed bool // whether an indexing expression appears: %[1]d.
|
||||
firstArg int // Index of first argument after the format in the Printf call.
|
||||
// Used only during parse.
|
||||
file *File
|
||||
call *ast.CallExpr
|
||||
argNum int // Which argument we're expecting to format now.
|
||||
indexPending bool // Whether we have an indexed argument that has not resolved.
|
||||
nbytes int // number of bytes of the format string consumed.
|
||||
}
|
||||
|
||||
// checkPrintf checks a call to a formatted print routine such as Printf.
|
||||
func (f *File) checkPrintf(call *ast.CallExpr, name string) {
|
||||
format, idx := formatString(f, call)
|
||||
if idx < 0 {
|
||||
if *verbose {
|
||||
f.Warn(call.Pos(), "can't check non-constant format in call to", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
firstArg := idx + 1 // Arguments are immediately after format string.
|
||||
if !strings.Contains(format, "%") {
|
||||
if len(call.Args) > firstArg {
|
||||
f.Badf(call.Pos(), "no formatting directive in %s call", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Hard part: check formats against args.
|
||||
argNum := firstArg
|
||||
indexed := false
|
||||
for i, w := 0, 0; i < len(format); i += w {
|
||||
w = 1
|
||||
if format[i] == '%' {
|
||||
state := f.parsePrintfVerb(call, name, format[i:], firstArg, argNum)
|
||||
if state == nil {
|
||||
return
|
||||
}
|
||||
w = len(state.format)
|
||||
if state.indexed {
|
||||
indexed = true
|
||||
}
|
||||
if !f.okPrintfArg(call, state) { // One error per format is enough.
|
||||
return
|
||||
}
|
||||
if len(state.argNums) > 0 {
|
||||
// Continue with the next sequential argument.
|
||||
argNum = state.argNums[len(state.argNums)-1] + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
// Dotdotdot is hard.
|
||||
if call.Ellipsis.IsValid() && argNum >= len(call.Args)-1 {
|
||||
return
|
||||
}
|
||||
// If the arguments were direct indexed, we assume the programmer knows what's up.
|
||||
// Otherwise, there should be no leftover arguments.
|
||||
if !indexed && argNum != len(call.Args) {
|
||||
expect := argNum - firstArg
|
||||
numArgs := len(call.Args) - firstArg
|
||||
f.Badf(call.Pos(), "wrong number of args for format in %s call: %d needed but %d args", name, expect, numArgs)
|
||||
}
|
||||
}
|
||||
|
||||
// parseFlags accepts any printf flags.
|
||||
func (s *formatState) parseFlags() {
|
||||
for s.nbytes < len(s.format) {
|
||||
switch c := s.format[s.nbytes]; c {
|
||||
case '#', '0', '+', '-', ' ':
|
||||
s.flags = append(s.flags, c)
|
||||
s.nbytes++
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scanNum advances through a decimal number if present.
|
||||
func (s *formatState) scanNum() {
|
||||
for ; s.nbytes < len(s.format); s.nbytes++ {
|
||||
c := s.format[s.nbytes]
|
||||
if c < '0' || '9' < c {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseIndex scans an index expression. It returns false if there is a syntax error.
|
||||
func (s *formatState) parseIndex() bool {
|
||||
if s.nbytes == len(s.format) || s.format[s.nbytes] != '[' {
|
||||
return true
|
||||
}
|
||||
// Argument index present.
|
||||
s.indexed = true
|
||||
s.nbytes++ // skip '['
|
||||
start := s.nbytes
|
||||
s.scanNum()
|
||||
if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' {
|
||||
s.file.Badf(s.call.Pos(), "illegal syntax for printf argument index")
|
||||
return false
|
||||
}
|
||||
arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32)
|
||||
if err != nil {
|
||||
s.file.Badf(s.call.Pos(), "illegal syntax for printf argument index: %s", err)
|
||||
return false
|
||||
}
|
||||
s.nbytes++ // skip ']'
|
||||
arg := int(arg32)
|
||||
arg += s.firstArg - 1 // We want to zero-index the actual arguments.
|
||||
s.argNum = arg
|
||||
s.indexPending = true
|
||||
return true
|
||||
}
|
||||
|
||||
// parseNum scans a width or precision (or *). It returns false if there's a bad index expression.
|
||||
func (s *formatState) parseNum() bool {
|
||||
if s.nbytes < len(s.format) && s.format[s.nbytes] == '*' {
|
||||
if s.indexPending { // Absorb it.
|
||||
s.indexPending = false
|
||||
}
|
||||
s.nbytes++
|
||||
s.argNums = append(s.argNums, s.argNum)
|
||||
s.argNum++
|
||||
} else {
|
||||
s.scanNum()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// parsePrecision scans for a precision. It returns false if there's a bad index expression.
|
||||
func (s *formatState) parsePrecision() bool {
|
||||
// If there's a period, there may be a precision.
|
||||
if s.nbytes < len(s.format) && s.format[s.nbytes] == '.' {
|
||||
s.flags = append(s.flags, '.') // Treat precision as a flag.
|
||||
s.nbytes++
|
||||
if !s.parseIndex() {
|
||||
return false
|
||||
}
|
||||
if !s.parseNum() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// parsePrintfVerb looks the formatting directive that begins the format string
|
||||
// and returns a formatState that encodes what the directive wants, without looking
|
||||
// at the actual arguments present in the call. The result is nil if there is an error.
|
||||
func (f *File) parsePrintfVerb(call *ast.CallExpr, name, format string, firstArg, argNum int) *formatState {
|
||||
state := &formatState{
|
||||
format: format,
|
||||
name: name,
|
||||
flags: make([]byte, 0, 5),
|
||||
argNum: argNum,
|
||||
argNums: make([]int, 0, 1),
|
||||
nbytes: 1, // There's guaranteed to be a percent sign.
|
||||
indexed: false,
|
||||
firstArg: firstArg,
|
||||
file: f,
|
||||
call: call,
|
||||
}
|
||||
// There may be flags.
|
||||
state.parseFlags()
|
||||
indexPending := false
|
||||
// There may be an index.
|
||||
if !state.parseIndex() {
|
||||
return nil
|
||||
}
|
||||
// There may be a width.
|
||||
if !state.parseNum() {
|
||||
return nil
|
||||
}
|
||||
// There may be a precision.
|
||||
if !state.parsePrecision() {
|
||||
return nil
|
||||
}
|
||||
// Now a verb, possibly prefixed by an index (which we may already have).
|
||||
if !indexPending && !state.parseIndex() {
|
||||
return nil
|
||||
}
|
||||
if state.nbytes == len(state.format) {
|
||||
f.Badf(call.Pos(), "missing verb at end of format string in %s call", name)
|
||||
return nil
|
||||
}
|
||||
verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:])
|
||||
state.verb = verb
|
||||
state.nbytes += w
|
||||
if verb != '%' {
|
||||
state.argNums = append(state.argNums, state.argNum)
|
||||
}
|
||||
state.format = state.format[:state.nbytes]
|
||||
return state
|
||||
}
|
||||
|
||||
// printfArgType encodes the types of expressions a printf verb accepts. It is a bitmask.
|
||||
type printfArgType int
|
||||
|
||||
const (
|
||||
argBool printfArgType = 1 << iota
|
||||
argInt
|
||||
argRune
|
||||
argString
|
||||
argFloat
|
||||
argComplex
|
||||
argPointer
|
||||
anyType printfArgType = ^0
|
||||
)
|
||||
|
||||
type printVerb struct {
|
||||
verb rune // User may provide verb through Formatter; could be a rune.
|
||||
flags string // known flags are all ASCII
|
||||
typ printfArgType
|
||||
}
|
||||
|
||||
// Common flag sets for printf verbs.
|
||||
const (
|
||||
noFlag = ""
|
||||
numFlag = " -+.0"
|
||||
sharpNumFlag = " -+.0#"
|
||||
allFlags = " -+.0#"
|
||||
)
|
||||
|
||||
// printVerbs identifies which flags are known to printf for each verb.
|
||||
// TODO: A type that implements Formatter may do what it wants, and vet
|
||||
// will complain incorrectly.
|
||||
var printVerbs = []printVerb{
|
||||
// '-' is a width modifier, always valid.
|
||||
// '.' is a precision for float, max width for strings.
|
||||
// '+' is required sign for numbers, Go format for %v.
|
||||
// '#' is alternate format for several verbs.
|
||||
// ' ' is spacer for numbers
|
||||
{'%', noFlag, 0},
|
||||
{'b', numFlag, argInt | argFloat | argComplex},
|
||||
{'c', "-", argRune | argInt},
|
||||
{'d', numFlag, argInt},
|
||||
{'e', numFlag, argFloat | argComplex},
|
||||
{'E', numFlag, argFloat | argComplex},
|
||||
{'f', numFlag, argFloat | argComplex},
|
||||
{'F', numFlag, argFloat | argComplex},
|
||||
{'g', numFlag, argFloat | argComplex},
|
||||
{'G', numFlag, argFloat | argComplex},
|
||||
{'o', sharpNumFlag, argInt},
|
||||
{'p', "-#", argPointer},
|
||||
{'q', " -+.0#", argRune | argInt | argString},
|
||||
{'s', " -+.0", argString},
|
||||
{'t', "-", argBool},
|
||||
{'T', "-", anyType},
|
||||
{'U', "-#", argRune | argInt},
|
||||
{'v', allFlags, anyType},
|
||||
{'x', sharpNumFlag, argRune | argInt | argString},
|
||||
{'X', sharpNumFlag, argRune | argInt | argString},
|
||||
}
|
||||
|
||||
// okPrintfArg compares the formatState to the arguments actually present,
|
||||
// reporting any discrepancies it can discern. If the final argument is ellipsissed,
|
||||
// there's little it can do for that.
|
||||
func (f *File) okPrintfArg(call *ast.CallExpr, state *formatState) (ok bool) {
|
||||
var v printVerb
|
||||
found := false
|
||||
// Linear scan is fast enough for a small list.
|
||||
for _, v = range printVerbs {
|
||||
if v.verb == state.verb {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
f.Badf(call.Pos(), "unrecognized printf verb %q", state.verb)
|
||||
return false
|
||||
}
|
||||
for _, flag := range state.flags {
|
||||
if !strings.ContainsRune(v.flags, rune(flag)) {
|
||||
f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", state.verb, flag)
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Verb is good. If len(state.argNums)>trueArgs, we have something like %.*s and all
|
||||
// but the final arg must be an integer.
|
||||
trueArgs := 1
|
||||
if state.verb == '%' {
|
||||
trueArgs = 0
|
||||
}
|
||||
nargs := len(state.argNums)
|
||||
for i := 0; i < nargs-trueArgs; i++ {
|
||||
argNum := state.argNums[i]
|
||||
if !f.argCanBeChecked(call, i, true, state) {
|
||||
return
|
||||
}
|
||||
arg := call.Args[argNum]
|
||||
if !f.matchArgType(argInt, nil, arg) {
|
||||
f.Badf(call.Pos(), "arg %s for * in printf format not of type int", f.gofmt(arg))
|
||||
return false
|
||||
}
|
||||
}
|
||||
if state.verb == '%' {
|
||||
return true
|
||||
}
|
||||
argNum := state.argNums[len(state.argNums)-1]
|
||||
if !f.argCanBeChecked(call, len(state.argNums)-1, false, state) {
|
||||
return false
|
||||
}
|
||||
arg := call.Args[argNum]
|
||||
if f.isFunctionValue(arg) && state.verb != 'p' && state.verb != 'T' {
|
||||
f.Badf(call.Pos(), "arg %s in printf call is a function value, not a function call", f.gofmt(arg))
|
||||
return false
|
||||
}
|
||||
if !f.matchArgType(v.typ, nil, arg) {
|
||||
typeString := ""
|
||||
if typ := f.pkg.types[arg].Type; typ != nil {
|
||||
typeString = typ.String()
|
||||
}
|
||||
f.Badf(call.Pos(), "arg %s for printf verb %%%c of wrong type: %s", f.gofmt(arg), state.verb, typeString)
|
||||
return false
|
||||
}
|
||||
if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) && f.recursiveStringer(arg) {
|
||||
f.Badf(call.Pos(), "arg %s for printf causes recursive call to String method", f.gofmt(arg))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// recursiveStringer reports whether the provided argument is r or &r for the
|
||||
// fmt.Stringer receiver identifier r.
|
||||
func (f *File) recursiveStringer(e ast.Expr) bool {
|
||||
if len(f.stringers) == 0 {
|
||||
return false
|
||||
}
|
||||
var obj *ast.Object
|
||||
switch e := e.(type) {
|
||||
case *ast.Ident:
|
||||
obj = e.Obj
|
||||
case *ast.UnaryExpr:
|
||||
if id, ok := e.X.(*ast.Ident); ok && e.Op == token.AND {
|
||||
obj = id.Obj
|
||||
}
|
||||
}
|
||||
|
||||
// It's unlikely to be a recursive stringer if it has a Format method.
|
||||
if typ := f.pkg.types[e].Type; typ != nil {
|
||||
// Not a perfect match; see issue 6259.
|
||||
if f.hasMethod(typ, "Format") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// We compare the underlying Object, which checks that the identifier
|
||||
// is the one we declared as the receiver for the String method in
|
||||
// which this printf appears.
|
||||
return f.stringers[obj]
|
||||
}
|
||||
|
||||
// isFunctionValue reports whether the expression is a function as opposed to a function call.
|
||||
// It is almost always a mistake to print a function value.
|
||||
func (f *File) isFunctionValue(e ast.Expr) bool {
|
||||
if typ := f.pkg.types[e].Type; typ != nil {
|
||||
_, ok := typ.(*types.Signature)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// argCanBeChecked reports whether the specified argument is statically present;
|
||||
// it may be beyond the list of arguments or in a terminal slice... argument, which
|
||||
// means we can't see it.
|
||||
func (f *File) argCanBeChecked(call *ast.CallExpr, formatArg int, isStar bool, state *formatState) bool {
|
||||
argNum := state.argNums[formatArg]
|
||||
if argNum < 0 {
|
||||
// Shouldn't happen, so catch it with prejudice.
|
||||
panic("negative arg num")
|
||||
}
|
||||
if argNum == 0 {
|
||||
f.Badf(call.Pos(), `index value [0] for %s("%s"); indexes start at 1`, state.name, state.format)
|
||||
return false
|
||||
}
|
||||
if argNum < len(call.Args)-1 {
|
||||
return true // Always OK.
|
||||
}
|
||||
if call.Ellipsis.IsValid() {
|
||||
return false // We just can't tell; there could be many more arguments.
|
||||
}
|
||||
if argNum < len(call.Args) {
|
||||
return true
|
||||
}
|
||||
// There are bad indexes in the format or there are fewer arguments than the format needs.
|
||||
// This is the argument number relative to the format: Printf("%s", "hi") will give 1 for the "hi".
|
||||
arg := argNum - state.firstArg + 1 // People think of arguments as 1-indexed.
|
||||
f.Badf(call.Pos(), `missing argument for %s("%s"): format reads arg %d, have only %d args`, state.name, state.format, arg, len(call.Args)-state.firstArg)
|
||||
return false
|
||||
}
|
||||
|
||||
// checkPrint checks a call to an unformatted print routine such as Println.
|
||||
func (f *File) checkPrint(call *ast.CallExpr, name string) {
|
||||
firstArg := 0
|
||||
typ := f.pkg.types[call.Fun].Type
|
||||
if typ == nil {
|
||||
// Skip checking functions with unknown type.
|
||||
return
|
||||
}
|
||||
if sig, ok := typ.(*types.Signature); ok {
|
||||
if !sig.Variadic() {
|
||||
// Skip checking non-variadic functions.
|
||||
return
|
||||
}
|
||||
params := sig.Params()
|
||||
firstArg = params.Len() - 1
|
||||
|
||||
typ := params.At(firstArg).Type()
|
||||
typ = typ.(*types.Slice).Elem()
|
||||
it, ok := typ.(*types.Interface)
|
||||
if !ok || !it.Empty() {
|
||||
// Skip variadic functions accepting non-interface{} args.
|
||||
return
|
||||
}
|
||||
}
|
||||
args := call.Args
|
||||
if len(args) <= firstArg {
|
||||
// Skip calls without variadic args.
|
||||
return
|
||||
}
|
||||
args = args[firstArg:]
|
||||
|
||||
// check for Println(os.Stderr, ...)
|
||||
if firstArg == 0 {
|
||||
if sel, ok := args[0].(*ast.SelectorExpr); ok {
|
||||
if x, ok := sel.X.(*ast.Ident); ok {
|
||||
if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
|
||||
f.Badf(call.Pos(), "first argument to %s is %s.%s", name, x.Name, sel.Sel.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
arg := args[0]
|
||||
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
||||
if strings.Contains(lit.Value, "%") {
|
||||
f.Badf(call.Pos(), "possible formatting directive in %s call", name)
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(name, "ln") {
|
||||
// The last item, if a string, should not have a newline.
|
||||
arg = args[len(args)-1]
|
||||
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
||||
if strings.HasSuffix(lit.Value, `\n"`) {
|
||||
f.Badf(call.Pos(), "%s call ends with newline", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, arg := range args {
|
||||
if f.isFunctionValue(arg) {
|
||||
f.Badf(call.Pos(), "arg %s in %s call is a function value, not a function call", f.gofmt(arg), name)
|
||||
}
|
||||
if f.recursiveStringer(arg) {
|
||||
f.Badf(call.Pos(), "arg %s in %s call causes recursive call to String method", f.gofmt(arg), name)
|
||||
}
|
||||
}
|
||||
}
|
||||
74
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/rangeloop.go
vendored
Normal file
74
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/rangeloop.go
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
This file contains the code to check range loop variables bound inside function
|
||||
literals that are deferred or launched in new goroutines. We only check
|
||||
instances where the defer or go statement is the last statement in the loop
|
||||
body, as otherwise we would need whole program analysis.
|
||||
|
||||
For example:
|
||||
|
||||
for i, v := range s {
|
||||
go func() {
|
||||
println(i, v) // not what you might expect
|
||||
}()
|
||||
}
|
||||
|
||||
See: https://golang.org/doc/go_faq.html#closures_and_goroutines
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func init() {
|
||||
register("rangeloops",
|
||||
"check that range loop variables are used correctly",
|
||||
checkRangeLoop,
|
||||
rangeStmt)
|
||||
}
|
||||
|
||||
// checkRangeLoop walks the body of the provided range statement, checking if
|
||||
// its index or value variables are used unsafely inside goroutines or deferred
|
||||
// function literals.
|
||||
func checkRangeLoop(f *File, node ast.Node) {
|
||||
n := node.(*ast.RangeStmt)
|
||||
key, _ := n.Key.(*ast.Ident)
|
||||
val, _ := n.Value.(*ast.Ident)
|
||||
if key == nil && val == nil {
|
||||
return
|
||||
}
|
||||
sl := n.Body.List
|
||||
if len(sl) == 0 {
|
||||
return
|
||||
}
|
||||
var last *ast.CallExpr
|
||||
switch s := sl[len(sl)-1].(type) {
|
||||
case *ast.GoStmt:
|
||||
last = s.Call
|
||||
case *ast.DeferStmt:
|
||||
last = s.Call
|
||||
default:
|
||||
return
|
||||
}
|
||||
lit, ok := last.Fun.(*ast.FuncLit)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ast.Inspect(lit.Body, func(n ast.Node) bool {
|
||||
id, ok := n.(*ast.Ident)
|
||||
if !ok || id.Obj == nil {
|
||||
return true
|
||||
}
|
||||
if f.pkg.types[id].Type == nil {
|
||||
// Not referring to a variable
|
||||
return true
|
||||
}
|
||||
if key != nil && id.Obj == key.Obj || val != nil && id.Obj == val.Obj {
|
||||
f.Bad(id.Pos(), "range variable", id.Name, "captured by func literal")
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
246
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/shadow.go
vendored
Normal file
246
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/shadow.go
vendored
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
// 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.
|
||||
|
||||
/*
|
||||
This file contains the code to check for shadowed variables.
|
||||
A shadowed variable is a variable declared in an inner scope
|
||||
with the same name and type as a variable in an outer scope,
|
||||
and where the outer variable is mentioned after the inner one
|
||||
is declared.
|
||||
|
||||
(This definition can be refined; the module generates too many
|
||||
false positives and is not yet enabled by default.)
|
||||
|
||||
For example:
|
||||
|
||||
func BadRead(f *os.File, buf []byte) error {
|
||||
var err error
|
||||
for {
|
||||
n, err := f.Read(buf) // shadows the function variable 'err'
|
||||
if err != nil {
|
||||
break // causes return of wrong value
|
||||
}
|
||||
foo(buf)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
var strictShadowing = flag.Bool("shadowstrict", false, "whether to be strict about shadowing; can be noisy")
|
||||
|
||||
func init() {
|
||||
register("shadow",
|
||||
"check for shadowed variables (experimental; must be set explicitly)",
|
||||
checkShadow,
|
||||
assignStmt, genDecl)
|
||||
experimental["shadow"] = true
|
||||
}
|
||||
|
||||
func checkShadow(f *File, node ast.Node) {
|
||||
switch n := node.(type) {
|
||||
case *ast.AssignStmt:
|
||||
checkShadowAssignment(f, n)
|
||||
case *ast.GenDecl:
|
||||
checkShadowDecl(f, n)
|
||||
}
|
||||
}
|
||||
|
||||
// Span stores the minimum range of byte positions in the file in which a
|
||||
// given variable (types.Object) is mentioned. It is lexically defined: it spans
|
||||
// from the beginning of its first mention to the end of its last mention.
|
||||
// A variable is considered shadowed (if *strictShadowing is off) only if the
|
||||
// shadowing variable is declared within the span of the shadowed variable.
|
||||
// In other words, if a variable is shadowed but not used after the shadowed
|
||||
// variable is declared, it is inconsequential and not worth complaining about.
|
||||
// This simple check dramatically reduces the nuisance rate for the shadowing
|
||||
// check, at least until something cleverer comes along.
|
||||
//
|
||||
// One wrinkle: A "naked return" is a silent use of a variable that the Span
|
||||
// will not capture, but the compilers catch naked returns of shadowed
|
||||
// variables so we don't need to.
|
||||
//
|
||||
// Cases this gets wrong (TODO):
|
||||
// - If a for loop's continuation statement mentions a variable redeclared in
|
||||
// the block, we should complain about it but don't.
|
||||
// - A variable declared inside a function literal can falsely be identified
|
||||
// as shadowing a variable in the outer function.
|
||||
//
|
||||
type Span struct {
|
||||
min token.Pos
|
||||
max token.Pos
|
||||
}
|
||||
|
||||
// contains reports whether the position is inside the span.
|
||||
func (s Span) contains(pos token.Pos) bool {
|
||||
return s.min <= pos && pos < s.max
|
||||
}
|
||||
|
||||
// growSpan expands the span for the object to contain the instance represented
|
||||
// by the identifier.
|
||||
func (pkg *Package) growSpan(ident *ast.Ident, obj types.Object) {
|
||||
if *strictShadowing {
|
||||
return // No need
|
||||
}
|
||||
pos := ident.Pos()
|
||||
end := ident.End()
|
||||
span, ok := pkg.spans[obj]
|
||||
if ok {
|
||||
if span.min > pos {
|
||||
span.min = pos
|
||||
}
|
||||
if span.max < end {
|
||||
span.max = end
|
||||
}
|
||||
} else {
|
||||
span = Span{pos, end}
|
||||
}
|
||||
pkg.spans[obj] = span
|
||||
}
|
||||
|
||||
// checkShadowAssignment checks for shadowing in a short variable declaration.
|
||||
func checkShadowAssignment(f *File, a *ast.AssignStmt) {
|
||||
if a.Tok != token.DEFINE {
|
||||
return
|
||||
}
|
||||
if f.idiomaticShortRedecl(a) {
|
||||
return
|
||||
}
|
||||
for _, expr := range a.Lhs {
|
||||
ident, ok := expr.(*ast.Ident)
|
||||
if !ok {
|
||||
f.Badf(expr.Pos(), "invalid AST: short variable declaration of non-identifier")
|
||||
return
|
||||
}
|
||||
checkShadowing(f, ident)
|
||||
}
|
||||
}
|
||||
|
||||
// idiomaticShortRedecl reports whether this short declaration can be ignored for
|
||||
// the purposes of shadowing, that is, that any redeclarations it contains are deliberate.
|
||||
func (f *File) idiomaticShortRedecl(a *ast.AssignStmt) bool {
|
||||
// Don't complain about deliberate redeclarations of the form
|
||||
// i := i
|
||||
// Such constructs are idiomatic in range loops to create a new variable
|
||||
// for each iteration. Another example is
|
||||
// switch n := n.(type)
|
||||
if len(a.Rhs) != len(a.Lhs) {
|
||||
return false
|
||||
}
|
||||
// We know it's an assignment, so the LHS must be all identifiers. (We check anyway.)
|
||||
for i, expr := range a.Lhs {
|
||||
lhs, ok := expr.(*ast.Ident)
|
||||
if !ok {
|
||||
f.Badf(expr.Pos(), "invalid AST: short variable declaration of non-identifier")
|
||||
return true // Don't do any more processing.
|
||||
}
|
||||
switch rhs := a.Rhs[i].(type) {
|
||||
case *ast.Ident:
|
||||
if lhs.Name != rhs.Name {
|
||||
return false
|
||||
}
|
||||
case *ast.TypeAssertExpr:
|
||||
if id, ok := rhs.X.(*ast.Ident); ok {
|
||||
if lhs.Name != id.Name {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// idiomaticRedecl reports whether this declaration spec can be ignored for
|
||||
// the purposes of shadowing, that is, that any redeclarations it contains are deliberate.
|
||||
func (f *File) idiomaticRedecl(d *ast.ValueSpec) bool {
|
||||
// Don't complain about deliberate redeclarations of the form
|
||||
// var i, j = i, j
|
||||
if len(d.Names) != len(d.Values) {
|
||||
return false
|
||||
}
|
||||
for i, lhs := range d.Names {
|
||||
if rhs, ok := d.Values[i].(*ast.Ident); ok {
|
||||
if lhs.Name != rhs.Name {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// checkShadowDecl checks for shadowing in a general variable declaration.
|
||||
func checkShadowDecl(f *File, d *ast.GenDecl) {
|
||||
if d.Tok != token.VAR {
|
||||
return
|
||||
}
|
||||
for _, spec := range d.Specs {
|
||||
valueSpec, ok := spec.(*ast.ValueSpec)
|
||||
if !ok {
|
||||
f.Badf(spec.Pos(), "invalid AST: var GenDecl not ValueSpec")
|
||||
return
|
||||
}
|
||||
// Don't complain about deliberate redeclarations of the form
|
||||
// var i = i
|
||||
if f.idiomaticRedecl(valueSpec) {
|
||||
return
|
||||
}
|
||||
for _, ident := range valueSpec.Names {
|
||||
checkShadowing(f, ident)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkShadowing checks whether the identifier shadows an identifier in an outer scope.
|
||||
func checkShadowing(f *File, ident *ast.Ident) {
|
||||
if ident.Name == "_" {
|
||||
// Can't shadow the blank identifier.
|
||||
return
|
||||
}
|
||||
obj := f.pkg.defs[ident]
|
||||
if obj == nil {
|
||||
return
|
||||
}
|
||||
// obj.Parent.Parent is the surrounding scope. If we can find another declaration
|
||||
// starting from there, we have a shadowed identifier.
|
||||
_, shadowed := obj.Parent().Parent().LookupParent(obj.Name(), obj.Pos())
|
||||
if shadowed == nil {
|
||||
return
|
||||
}
|
||||
// Don't complain if it's shadowing a universe-declared identifier; that's fine.
|
||||
if shadowed.Parent() == types.Universe {
|
||||
return
|
||||
}
|
||||
if *strictShadowing {
|
||||
// The shadowed identifier must appear before this one to be an instance of shadowing.
|
||||
if shadowed.Pos() > ident.Pos() {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Don't complain if the span of validity of the shadowed identifier doesn't include
|
||||
// the shadowing identifier.
|
||||
span, ok := f.pkg.spans[shadowed]
|
||||
if !ok {
|
||||
f.Badf(ident.Pos(), "internal error: no range for %q", ident.Name)
|
||||
return
|
||||
}
|
||||
if !span.contains(ident.Pos()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Don't complain if the types differ: that implies the programmer really wants two different things.
|
||||
if types.Identical(obj.Type(), shadowed.Type()) {
|
||||
f.Badf(ident.Pos(), "declaration of %q shadows declaration at %s", obj.Name(), f.loc(shadowed.Pos()))
|
||||
}
|
||||
}
|
||||
82
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/shift.go
vendored
Normal file
82
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/shift.go
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
This file contains the code to check for suspicious shifts.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("shift",
|
||||
"check for useless shifts",
|
||||
checkShift,
|
||||
binaryExpr, assignStmt)
|
||||
}
|
||||
|
||||
func checkShift(f *File, node ast.Node) {
|
||||
switch node := node.(type) {
|
||||
case *ast.BinaryExpr:
|
||||
if node.Op == token.SHL || node.Op == token.SHR {
|
||||
checkLongShift(f, node, node.X, node.Y)
|
||||
}
|
||||
case *ast.AssignStmt:
|
||||
if len(node.Lhs) != 1 || len(node.Rhs) != 1 {
|
||||
return
|
||||
}
|
||||
if node.Tok == token.SHL_ASSIGN || node.Tok == token.SHR_ASSIGN {
|
||||
checkLongShift(f, node, node.Lhs[0], node.Rhs[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkLongShift checks if shift or shift-assign operations shift by more than
|
||||
// the length of the underlying variable.
|
||||
func checkLongShift(f *File, node ast.Node, x, y ast.Expr) {
|
||||
v := f.pkg.types[y].Value
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
amt, ok := constant.Int64Val(v)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
t := f.pkg.types[x].Type
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
b, ok := t.Underlying().(*types.Basic)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var size int64
|
||||
var msg string
|
||||
switch b.Kind() {
|
||||
case types.Uint8, types.Int8:
|
||||
size = 8
|
||||
case types.Uint16, types.Int16:
|
||||
size = 16
|
||||
case types.Uint32, types.Int32:
|
||||
size = 32
|
||||
case types.Uint64, types.Int64:
|
||||
size = 64
|
||||
case types.Int, types.Uint, types.Uintptr:
|
||||
// These types may be as small as 32 bits, but no smaller.
|
||||
size = 32
|
||||
msg = "might be "
|
||||
default:
|
||||
return
|
||||
}
|
||||
if amt >= size {
|
||||
ident := f.gofmt(x)
|
||||
f.Badf(node.Pos(), "%s %stoo small for shift of %d", ident, msg, amt)
|
||||
}
|
||||
}
|
||||
122
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/structtag.go
vendored
Normal file
122
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/structtag.go
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains the test for canonical struct tags.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("structtags",
|
||||
"check that struct field tags have canonical format and apply to exported fields as needed",
|
||||
checkCanonicalFieldTag,
|
||||
field)
|
||||
}
|
||||
|
||||
// checkCanonicalFieldTag checks a struct field tag.
|
||||
func checkCanonicalFieldTag(f *File, node ast.Node) {
|
||||
field := node.(*ast.Field)
|
||||
if field.Tag == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tag, err := strconv.Unquote(field.Tag.Value)
|
||||
if err != nil {
|
||||
f.Badf(field.Pos(), "unable to read struct tag %s", field.Tag.Value)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateStructTag(tag); err != nil {
|
||||
f.Badf(field.Pos(), "struct field tag %s not compatible with reflect.StructTag.Get: %s", field.Tag.Value, err)
|
||||
}
|
||||
|
||||
// Check for use of json or xml tags with unexported fields.
|
||||
|
||||
// Embedded struct. Nothing to do for now, but that
|
||||
// may change, depending on what happens with issue 7363.
|
||||
if len(field.Names) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if field.Names[0].IsExported() {
|
||||
return
|
||||
}
|
||||
|
||||
st := reflect.StructTag(tag)
|
||||
for _, enc := range [...]string{"json", "xml"} {
|
||||
if st.Get(enc) != "" {
|
||||
f.Badf(field.Pos(), "struct field %s has %s tag but is not exported", field.Names[0].Name, enc)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errTagSyntax = errors.New("bad syntax for struct tag pair")
|
||||
errTagKeySyntax = errors.New("bad syntax for struct tag key")
|
||||
errTagValueSyntax = errors.New("bad syntax for struct tag value")
|
||||
)
|
||||
|
||||
// validateStructTag parses the struct tag and returns an error if it is not
|
||||
// in the canonical format, which is a space-separated list of key:"value"
|
||||
// settings. The value may contain spaces.
|
||||
func validateStructTag(tag string) error {
|
||||
// This code is based on the StructTag.Get code in package reflect.
|
||||
|
||||
for tag != "" {
|
||||
// Skip leading space.
|
||||
i := 0
|
||||
for i < len(tag) && tag[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
tag = tag[i:]
|
||||
if tag == "" {
|
||||
break
|
||||
}
|
||||
|
||||
// Scan to colon. A space, a quote or a control character is a syntax error.
|
||||
// Strictly speaking, control chars include the range [0x7f, 0x9f], not just
|
||||
// [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
|
||||
// as it is simpler to inspect the tag's bytes than the tag's runes.
|
||||
i = 0
|
||||
for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
|
||||
i++
|
||||
}
|
||||
if i == 0 {
|
||||
return errTagKeySyntax
|
||||
}
|
||||
if i+1 >= len(tag) || tag[i] != ':' {
|
||||
return errTagSyntax
|
||||
}
|
||||
if tag[i+1] != '"' {
|
||||
return errTagValueSyntax
|
||||
}
|
||||
tag = tag[i+1:]
|
||||
|
||||
// Scan quoted string to find value.
|
||||
i = 1
|
||||
for i < len(tag) && tag[i] != '"' {
|
||||
if tag[i] == '\\' {
|
||||
i++
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i >= len(tag) {
|
||||
return errTagValueSyntax
|
||||
}
|
||||
qvalue := tag[:i+1]
|
||||
tag = tag[i+1:]
|
||||
|
||||
if _, err := strconv.Unquote(qvalue); err != nil {
|
||||
return errTagValueSyntax
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
187
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/tests.go
vendored
Normal file
187
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/tests.go
vendored
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// 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 main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("tests",
|
||||
"check for common mistaken usages of tests/documentation examples",
|
||||
checkTestFunctions,
|
||||
funcDecl)
|
||||
}
|
||||
|
||||
func isExampleSuffix(s string) bool {
|
||||
r, size := utf8.DecodeRuneInString(s)
|
||||
return size > 0 && unicode.IsLower(r)
|
||||
}
|
||||
|
||||
func isTestSuffix(name string) bool {
|
||||
if len(name) == 0 {
|
||||
// "Test" is ok.
|
||||
return true
|
||||
}
|
||||
r, _ := utf8.DecodeRuneInString(name)
|
||||
return !unicode.IsLower(r)
|
||||
}
|
||||
|
||||
func isTestParam(typ ast.Expr, wantType string) bool {
|
||||
ptr, ok := typ.(*ast.StarExpr)
|
||||
if !ok {
|
||||
// Not a pointer.
|
||||
return false
|
||||
}
|
||||
// No easy way of making sure it's a *testing.T or *testing.B:
|
||||
// ensure the name of the type matches.
|
||||
if name, ok := ptr.X.(*ast.Ident); ok {
|
||||
return name.Name == wantType
|
||||
}
|
||||
if sel, ok := ptr.X.(*ast.SelectorExpr); ok {
|
||||
return sel.Sel.Name == wantType
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func lookup(name string, scopes []*types.Scope) types.Object {
|
||||
for _, scope := range scopes {
|
||||
if o := scope.Lookup(name); o != nil {
|
||||
return o
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extendedScope(f *File) []*types.Scope {
|
||||
scopes := []*types.Scope{f.pkg.typesPkg.Scope()}
|
||||
if f.basePkg != nil {
|
||||
scopes = append(scopes, f.basePkg.typesPkg.Scope())
|
||||
} else {
|
||||
// If basePkg is not specified (e.g. when checking a single file) try to
|
||||
// find it among imports.
|
||||
pkgName := f.pkg.typesPkg.Name()
|
||||
if strings.HasSuffix(pkgName, "_test") {
|
||||
basePkgName := strings.TrimSuffix(pkgName, "_test")
|
||||
for _, p := range f.pkg.typesPkg.Imports() {
|
||||
if p.Name() == basePkgName {
|
||||
scopes = append(scopes, p.Scope())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return scopes
|
||||
}
|
||||
|
||||
func checkExample(fn *ast.FuncDecl, f *File, report reporter) {
|
||||
fnName := fn.Name.Name
|
||||
if params := fn.Type.Params; len(params.List) != 0 {
|
||||
report("%s should be niladic", fnName)
|
||||
}
|
||||
if results := fn.Type.Results; results != nil && len(results.List) != 0 {
|
||||
report("%s should return nothing", fnName)
|
||||
}
|
||||
|
||||
if filesRun && !includesNonTest {
|
||||
// The coherence checks between a test and the package it tests
|
||||
// will report false positives if no non-test files have
|
||||
// been provided.
|
||||
return
|
||||
}
|
||||
|
||||
if fnName == "Example" {
|
||||
// Nothing more to do.
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
exName = strings.TrimPrefix(fnName, "Example")
|
||||
elems = strings.SplitN(exName, "_", 3)
|
||||
ident = elems[0]
|
||||
obj = lookup(ident, extendedScope(f))
|
||||
)
|
||||
if ident != "" && obj == nil {
|
||||
// Check ExampleFoo and ExampleBadFoo.
|
||||
report("%s refers to unknown identifier: %s", fnName, ident)
|
||||
// Abort since obj is absent and no subsequent checks can be performed.
|
||||
return
|
||||
}
|
||||
if len(elems) < 2 {
|
||||
// Nothing more to do.
|
||||
return
|
||||
}
|
||||
|
||||
if ident == "" {
|
||||
// Check Example_suffix and Example_BadSuffix.
|
||||
if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) {
|
||||
report("%s has malformed example suffix: %s", fnName, residual)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
mmbr := elems[1]
|
||||
if !isExampleSuffix(mmbr) {
|
||||
// Check ExampleFoo_Method and ExampleFoo_BadMethod.
|
||||
if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj == nil {
|
||||
report("%s refers to unknown field or method: %s.%s", fnName, ident, mmbr)
|
||||
}
|
||||
}
|
||||
if len(elems) == 3 && !isExampleSuffix(elems[2]) {
|
||||
// Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix.
|
||||
report("%s has malformed example suffix: %s", fnName, elems[2])
|
||||
}
|
||||
}
|
||||
|
||||
func checkTest(fn *ast.FuncDecl, prefix string, report reporter) {
|
||||
// Want functions with 0 results and 1 parameter.
|
||||
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
|
||||
fn.Type.Params == nil ||
|
||||
len(fn.Type.Params.List) != 1 ||
|
||||
len(fn.Type.Params.List[0].Names) > 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// The param must look like a *testing.T or *testing.B.
|
||||
if !isTestParam(fn.Type.Params.List[0].Type, prefix[:1]) {
|
||||
return
|
||||
}
|
||||
|
||||
if !isTestSuffix(fn.Name.Name[len(prefix):]) {
|
||||
report("%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
type reporter func(format string, args ...interface{})
|
||||
|
||||
// checkTestFunctions walks Test, Benchmark and Example functions checking
|
||||
// malformed names, wrong signatures and examples documenting inexistent
|
||||
// identifiers.
|
||||
func checkTestFunctions(f *File, node ast.Node) {
|
||||
if !strings.HasSuffix(f.name, "_test.go") {
|
||||
return
|
||||
}
|
||||
|
||||
fn, ok := node.(*ast.FuncDecl)
|
||||
if !ok || fn.Recv != nil {
|
||||
// Ignore non-functions or functions with receivers.
|
||||
return
|
||||
}
|
||||
|
||||
report := func(format string, args ...interface{}) { f.Badf(node.Pos(), format, args...) }
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(fn.Name.Name, "Example"):
|
||||
checkExample(fn, f, report)
|
||||
case strings.HasPrefix(fn.Name.Name, "Test"):
|
||||
checkTest(fn, "Test", report)
|
||||
case strings.HasPrefix(fn.Name.Name, "Benchmark"):
|
||||
checkTest(fn, "Benchmark", report)
|
||||
}
|
||||
}
|
||||
281
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/types.go
vendored
Normal file
281
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/types.go
vendored
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains the pieces of the tool that use typechecking from the go/types package.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/importer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// stdImporter is the importer we use to import packages.
|
||||
// It is created during initialization so that all packages
|
||||
// are imported by the same importer.
|
||||
var stdImporter = importer.Default()
|
||||
|
||||
var (
|
||||
errorType *types.Interface
|
||||
stringerType *types.Interface // possibly nil
|
||||
formatterType *types.Interface // possibly nil
|
||||
)
|
||||
|
||||
func init() {
|
||||
errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
|
||||
|
||||
if typ := importType("fmt", "Stringer"); typ != nil {
|
||||
stringerType = typ.Underlying().(*types.Interface)
|
||||
}
|
||||
|
||||
if typ := importType("fmt", "Formatter"); typ != nil {
|
||||
formatterType = typ.Underlying().(*types.Interface)
|
||||
}
|
||||
}
|
||||
|
||||
// importType returns the type denoted by the qualified identifier
|
||||
// path.name, and adds the respective package to the imports map
|
||||
// as a side effect. In case of an error, importType returns nil.
|
||||
func importType(path, name string) types.Type {
|
||||
pkg, err := stdImporter.Import(path)
|
||||
if err != nil {
|
||||
// This can happen if the package at path hasn't been compiled yet.
|
||||
warnf("import failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
if obj, ok := pkg.Scope().Lookup(name).(*types.TypeName); ok {
|
||||
return obj.Type()
|
||||
}
|
||||
warnf("invalid type name %q", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error {
|
||||
pkg.defs = make(map[*ast.Ident]types.Object)
|
||||
pkg.uses = make(map[*ast.Ident]types.Object)
|
||||
pkg.selectors = make(map[*ast.SelectorExpr]*types.Selection)
|
||||
pkg.spans = make(map[types.Object]Span)
|
||||
pkg.types = make(map[ast.Expr]types.TypeAndValue)
|
||||
config := types.Config{
|
||||
// We use the same importer for all imports to ensure that
|
||||
// everybody sees identical packages for the given paths.
|
||||
Importer: stdImporter,
|
||||
// By providing a Config with our own error function, it will continue
|
||||
// past the first error. There is no need for that function to do anything.
|
||||
Error: func(error) {},
|
||||
}
|
||||
info := &types.Info{
|
||||
Selections: pkg.selectors,
|
||||
Types: pkg.types,
|
||||
Defs: pkg.defs,
|
||||
Uses: pkg.uses,
|
||||
}
|
||||
typesPkg, err := config.Check(pkg.path, fs, astFiles, info)
|
||||
pkg.typesPkg = typesPkg
|
||||
// update spans
|
||||
for id, obj := range pkg.defs {
|
||||
pkg.growSpan(id, obj)
|
||||
}
|
||||
for id, obj := range pkg.uses {
|
||||
pkg.growSpan(id, obj)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// matchArgType reports an error if printf verb t is not appropriate
|
||||
// for operand arg.
|
||||
//
|
||||
// typ is used only for recursive calls; external callers must supply nil.
|
||||
//
|
||||
// (Recursion arises from the compound types {map,chan,slice} which
|
||||
// may be printed with %d etc. if that is appropriate for their element
|
||||
// types.)
|
||||
func (f *File) matchArgType(t printfArgType, typ types.Type, arg ast.Expr) bool {
|
||||
return f.matchArgTypeInternal(t, typ, arg, make(map[types.Type]bool))
|
||||
}
|
||||
|
||||
// matchArgTypeInternal is the internal version of matchArgType. It carries a map
|
||||
// remembering what types are in progress so we don't recur when faced with recursive
|
||||
// types or mutually recursive types.
|
||||
func (f *File) matchArgTypeInternal(t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool {
|
||||
// %v, %T accept any argument type.
|
||||
if t == anyType {
|
||||
return true
|
||||
}
|
||||
if typ == nil {
|
||||
// external call
|
||||
typ = f.pkg.types[arg].Type
|
||||
if typ == nil {
|
||||
return true // probably a type check problem
|
||||
}
|
||||
}
|
||||
// If the type implements fmt.Formatter, we have nothing to check.
|
||||
// formatterTyp may be nil - be conservative and check for Format method in that case.
|
||||
if formatterType != nil && types.Implements(typ, formatterType) || f.hasMethod(typ, "Format") {
|
||||
return true
|
||||
}
|
||||
// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
|
||||
if t&argString != 0 {
|
||||
if types.AssertableTo(errorType, typ) || stringerType != nil && types.AssertableTo(stringerType, typ) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
typ = typ.Underlying()
|
||||
if inProgress[typ] {
|
||||
// We're already looking at this type. The call that started it will take care of it.
|
||||
return true
|
||||
}
|
||||
inProgress[typ] = true
|
||||
|
||||
switch typ := typ.(type) {
|
||||
case *types.Signature:
|
||||
return t&argPointer != 0
|
||||
|
||||
case *types.Map:
|
||||
// Recur: map[int]int matches %d.
|
||||
return t&argPointer != 0 ||
|
||||
(f.matchArgTypeInternal(t, typ.Key(), arg, inProgress) && f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress))
|
||||
|
||||
case *types.Chan:
|
||||
return t&argPointer != 0
|
||||
|
||||
case *types.Array:
|
||||
// Same as slice.
|
||||
if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 {
|
||||
return true // %s matches []byte
|
||||
}
|
||||
// Recur: []int matches %d.
|
||||
return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem().Underlying(), arg, inProgress)
|
||||
|
||||
case *types.Slice:
|
||||
// Same as array.
|
||||
if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 {
|
||||
return true // %s matches []byte
|
||||
}
|
||||
// Recur: []int matches %d. But watch out for
|
||||
// type T []T
|
||||
// If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below.
|
||||
return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress)
|
||||
|
||||
case *types.Pointer:
|
||||
// Ugly, but dealing with an edge case: a known pointer to an invalid type,
|
||||
// probably something from a failed import.
|
||||
if typ.Elem().String() == "invalid type" {
|
||||
if *verbose {
|
||||
f.Warnf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", f.gofmt(arg))
|
||||
}
|
||||
return true // special case
|
||||
}
|
||||
// If it's actually a pointer with %p, it prints as one.
|
||||
if t == argPointer {
|
||||
return true
|
||||
}
|
||||
// If it's pointer to struct, that's equivalent in our analysis to whether we can print the struct.
|
||||
if str, ok := typ.Elem().Underlying().(*types.Struct); ok {
|
||||
return f.matchStructArgType(t, str, arg, inProgress)
|
||||
}
|
||||
// The rest can print with %p as pointers, or as integers with %x etc.
|
||||
return t&(argInt|argPointer) != 0
|
||||
|
||||
case *types.Struct:
|
||||
return f.matchStructArgType(t, typ, arg, inProgress)
|
||||
|
||||
case *types.Interface:
|
||||
// If the static type of the argument is empty interface, there's little we can do.
|
||||
// Example:
|
||||
// func f(x interface{}) { fmt.Printf("%s", x) }
|
||||
// Whether x is valid for %s depends on the type of the argument to f. One day
|
||||
// we will be able to do better. For now, we assume that empty interface is OK
|
||||
// but non-empty interfaces, with Stringer and Error handled above, are errors.
|
||||
return typ.NumMethods() == 0
|
||||
|
||||
case *types.Basic:
|
||||
switch typ.Kind() {
|
||||
case types.UntypedBool,
|
||||
types.Bool:
|
||||
return t&argBool != 0
|
||||
|
||||
case types.UntypedInt,
|
||||
types.Int,
|
||||
types.Int8,
|
||||
types.Int16,
|
||||
types.Int32,
|
||||
types.Int64,
|
||||
types.Uint,
|
||||
types.Uint8,
|
||||
types.Uint16,
|
||||
types.Uint32,
|
||||
types.Uint64,
|
||||
types.Uintptr:
|
||||
return t&argInt != 0
|
||||
|
||||
case types.UntypedFloat,
|
||||
types.Float32,
|
||||
types.Float64:
|
||||
return t&argFloat != 0
|
||||
|
||||
case types.UntypedComplex,
|
||||
types.Complex64,
|
||||
types.Complex128:
|
||||
return t&argComplex != 0
|
||||
|
||||
case types.UntypedString,
|
||||
types.String:
|
||||
return t&argString != 0
|
||||
|
||||
case types.UnsafePointer:
|
||||
return t&(argPointer|argInt) != 0
|
||||
|
||||
case types.UntypedRune:
|
||||
return t&(argInt|argRune) != 0
|
||||
|
||||
case types.UntypedNil:
|
||||
return t&argPointer != 0 // TODO?
|
||||
|
||||
case types.Invalid:
|
||||
if *verbose {
|
||||
f.Warnf(arg.Pos(), "printf argument %v has invalid or unknown type", f.gofmt(arg))
|
||||
}
|
||||
return true // Probably a type check problem.
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// hasBasicType reports whether x's type is a types.Basic with the given kind.
|
||||
func (f *File) hasBasicType(x ast.Expr, kind types.BasicKind) bool {
|
||||
t := f.pkg.types[x].Type
|
||||
if t != nil {
|
||||
t = t.Underlying()
|
||||
}
|
||||
b, ok := t.(*types.Basic)
|
||||
return ok && b.Kind() == kind
|
||||
}
|
||||
|
||||
// matchStructArgType reports whether all the elements of the struct match the expected
|
||||
// type. For instance, with "%d" all the elements must be printable with the "%d" format.
|
||||
func (f *File) matchStructArgType(t printfArgType, typ *types.Struct, arg ast.Expr, inProgress map[types.Type]bool) bool {
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
if !f.matchArgTypeInternal(t, typ.Field(i).Type(), arg, inProgress) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// hasMethod reports whether the type contains a method with the given name.
|
||||
// It is part of the workaround for Formatters and should be deleted when
|
||||
// that workaround is no longer necessary.
|
||||
// TODO: This could be better once issue 6259 is fixed.
|
||||
func (f *File) hasMethod(typ types.Type, name string) bool {
|
||||
// assume we have an addressable variable of type typ
|
||||
obj, _, _ := types.LookupFieldOrMethod(typ, true, f.pkg.typesPkg, name)
|
||||
_, ok := obj.(*types.Func)
|
||||
return ok
|
||||
}
|
||||
97
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/unsafeptr.go
vendored
Normal file
97
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/unsafeptr.go
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Check for invalid uintptr -> unsafe.Pointer conversions.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("unsafeptr",
|
||||
"check for misuse of unsafe.Pointer",
|
||||
checkUnsafePointer,
|
||||
callExpr)
|
||||
}
|
||||
|
||||
func checkUnsafePointer(f *File, node ast.Node) {
|
||||
x := node.(*ast.CallExpr)
|
||||
if len(x.Args) != 1 {
|
||||
return
|
||||
}
|
||||
if f.hasBasicType(x.Fun, types.UnsafePointer) && f.hasBasicType(x.Args[0], types.Uintptr) && !f.isSafeUintptr(x.Args[0]) {
|
||||
f.Badf(x.Pos(), "possible misuse of unsafe.Pointer")
|
||||
}
|
||||
}
|
||||
|
||||
// isSafeUintptr reports whether x - already known to be a uintptr -
|
||||
// is safe to convert to unsafe.Pointer. It is safe if x is itself derived
|
||||
// directly from an unsafe.Pointer via conversion and pointer arithmetic
|
||||
// or if x is the result of reflect.Value.Pointer or reflect.Value.UnsafeAddr
|
||||
// or obtained from the Data field of a *reflect.SliceHeader or *reflect.StringHeader.
|
||||
func (f *File) isSafeUintptr(x ast.Expr) bool {
|
||||
switch x := x.(type) {
|
||||
case *ast.ParenExpr:
|
||||
return f.isSafeUintptr(x.X)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
switch x.Sel.Name {
|
||||
case "Data":
|
||||
// reflect.SliceHeader and reflect.StringHeader are okay,
|
||||
// but only if they are pointing at a real slice or string.
|
||||
// It's not okay to do:
|
||||
// var x SliceHeader
|
||||
// x.Data = uintptr(unsafe.Pointer(...))
|
||||
// ... use x ...
|
||||
// p := unsafe.Pointer(x.Data)
|
||||
// because in the middle the garbage collector doesn't
|
||||
// see x.Data as a pointer and so x.Data may be dangling
|
||||
// by the time we get to the conversion at the end.
|
||||
// For now approximate by saying that *Header is okay
|
||||
// but Header is not.
|
||||
pt, ok := f.pkg.types[x.X].Type.(*types.Pointer)
|
||||
if ok {
|
||||
t, ok := pt.Elem().(*types.Named)
|
||||
if ok && t.Obj().Pkg().Path() == "reflect" {
|
||||
switch t.Obj().Name() {
|
||||
case "StringHeader", "SliceHeader":
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.CallExpr:
|
||||
switch len(x.Args) {
|
||||
case 0:
|
||||
// maybe call to reflect.Value.Pointer or reflect.Value.UnsafeAddr.
|
||||
sel, ok := x.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
switch sel.Sel.Name {
|
||||
case "Pointer", "UnsafeAddr":
|
||||
t, ok := f.pkg.types[sel.X].Type.(*types.Named)
|
||||
if ok && t.Obj().Pkg().Path() == "reflect" && t.Obj().Name() == "Value" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
case 1:
|
||||
// maybe conversion of uintptr to unsafe.Pointer
|
||||
return f.hasBasicType(x.Fun, types.Uintptr) && f.hasBasicType(x.Args[0], types.UnsafePointer)
|
||||
}
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
switch x.Op {
|
||||
case token.ADD, token.SUB:
|
||||
return f.isSafeUintptr(x.X) && !f.isSafeUintptr(x.Y)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
93
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/unused.go
vendored
Normal file
93
vendor/src/github.com/alecthomas/gometalinter/_linters/src/github.com/dnephin/govet/unused.go
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// 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.
|
||||
|
||||
// This file defines the check for unused results of calls to certain
|
||||
// pure functions.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var unusedFuncsFlag = flag.String("unusedfuncs",
|
||||
"errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse",
|
||||
"comma-separated list of functions whose results must be used")
|
||||
|
||||
var unusedStringMethodsFlag = flag.String("unusedstringmethods",
|
||||
"Error,String",
|
||||
"comma-separated list of names of methods of type func() string whose results must be used")
|
||||
|
||||
func init() {
|
||||
register("unusedresult",
|
||||
"check for unused result of calls to functions in -unusedfuncs list and methods in -unusedstringmethods list",
|
||||
checkUnusedResult,
|
||||
exprStmt)
|
||||
}
|
||||
|
||||
// func() string
|
||||
var sigNoArgsStringResult = types.NewSignature(nil, nil,
|
||||
types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])),
|
||||
false)
|
||||
|
||||
var unusedFuncs = make(map[string]bool)
|
||||
var unusedStringMethods = make(map[string]bool)
|
||||
|
||||
func initUnusedFlags() {
|
||||
commaSplit := func(s string, m map[string]bool) {
|
||||
if s != "" {
|
||||
for _, name := range strings.Split(s, ",") {
|
||||
if len(name) == 0 {
|
||||
flag.Usage()
|
||||
}
|
||||
m[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
commaSplit(*unusedFuncsFlag, unusedFuncs)
|
||||
commaSplit(*unusedStringMethodsFlag, unusedStringMethods)
|
||||
}
|
||||
|
||||
func checkUnusedResult(f *File, n ast.Node) {
|
||||
call, ok := unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr)
|
||||
if !ok {
|
||||
return // not a call statement
|
||||
}
|
||||
fun := unparen(call.Fun)
|
||||
|
||||
if f.pkg.types[fun].IsType() {
|
||||
return // a conversion, not a call
|
||||
}
|
||||
|
||||
selector, ok := fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return // neither a method call nor a qualified ident
|
||||
}
|
||||
|
||||
sel, ok := f.pkg.selectors[selector]
|
||||
if ok && sel.Kind() == types.MethodVal {
|
||||
// method (e.g. foo.String())
|
||||
obj := sel.Obj().(*types.Func)
|
||||
sig := sel.Type().(*types.Signature)
|
||||
if types.Identical(sig, sigNoArgsStringResult) {
|
||||
if unusedStringMethods[obj.Name()] {
|
||||
f.Badf(call.Lparen, "result of (%s).%s call not used",
|
||||
sig.Recv().Type(), obj.Name())
|
||||
}
|
||||
}
|
||||
} else if !ok {
|
||||
// package-qualified function (e.g. fmt.Errorf)
|
||||
obj, _ := f.pkg.uses[selector.Sel]
|
||||
if obj, ok := obj.(*types.Func); ok {
|
||||
qname := obj.Pkg().Path() + "." + obj.Name()
|
||||
if unusedFuncs[qname] {
|
||||
f.Badf(call.Lparen, "result of %v call not used", qname)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// +build go1.6
|
||||
// +build go1.6,!go1.9
|
||||
|
||||
package gotool
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.9
|
||||
|
||||
package load
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// hasPathPrefix reports whether the path s begins with the
|
||||
// elements in prefix.
|
||||
func hasPathPrefix(s, prefix string) bool {
|
||||
switch {
|
||||
default:
|
||||
return false
|
||||
case len(s) == len(prefix):
|
||||
return s == prefix
|
||||
case len(s) > len(prefix):
|
||||
if prefix != "" && prefix[len(prefix)-1] == '/' {
|
||||
return strings.HasPrefix(s, prefix)
|
||||
}
|
||||
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue