mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-12 09:23:09 -06:00
Merge remote-tracking branch 'upstream/master' into remi/check-roomid-doesnt-exist-when-creating
This commit is contained in:
commit
fe5029f688
14
.travis.yml
14
.travis.yml
|
|
@ -1,6 +1,12 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.8
|
- 1.8
|
||||||
|
- 1.9
|
||||||
|
|
||||||
|
env:
|
||||||
|
- TEST_SUITE="lint"
|
||||||
|
- TEST_SUITE="unit-test"
|
||||||
|
- TEST_SUITE="integ-test"
|
||||||
|
|
||||||
sudo: false
|
sudo: false
|
||||||
|
|
||||||
|
|
@ -8,9 +14,6 @@ sudo: false
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- openssl
|
|
||||||
postgresql: "9.5"
|
postgresql: "9.5"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
@ -19,12 +22,7 @@ services:
|
||||||
install:
|
install:
|
||||||
- go get github.com/constabulary/gb/...
|
- 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:
|
script:
|
||||||
- ./scripts/install-local-kafka.sh
|
|
||||||
- ./scripts/travis-test.sh
|
- ./scripts/travis-test.sh
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@ for that line or statement using a [comment directive](https://github.com/alecth
|
||||||
`// nolint: gocyclo`. This should be used sparingly and only when its clear
|
`// nolint: gocyclo`. This should be used sparingly and only when its clear
|
||||||
that the lint warning is spurious.
|
that the lint warning is spurious.
|
||||||
|
|
||||||
|
The linters are vendored, and can be run using [scripts/find-lint.sh](scripts/find-lint.sh)
|
||||||
|
(see file for docs) or as part of a build/test/lint cycle using
|
||||||
|
[scripts/build-test-lint.sh](scripts/build-test-lint.sh).
|
||||||
|
|
||||||
|
|
||||||
## HTTP Error Handling
|
## HTTP Error Handling
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ instance of dendrite, and [CODE_STYLE.md](CODE_STYLE.md) for the code style
|
||||||
guide.
|
guide.
|
||||||
|
|
||||||
We use `gb` for managing our dependencies, so `gb build` and `gb test` is how
|
We use `gb` for managing our dependencies, so `gb build` and `gb test` is how
|
||||||
to build dendrite and run the unit tests respectively.
|
to build dendrite and run the unit tests respectively. There are [scripts](scripts)
|
||||||
|
for [linting](scripts/find-lint.sh) and doing a [build/test/lint run](scripts/build-test-lint.sh).
|
||||||
|
|
||||||
|
|
||||||
## Picking Things To Do
|
## Picking Things To Do
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,24 @@ media:
|
||||||
height: 600
|
height: 600
|
||||||
method: scale
|
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
|
# The config for communicating with kafka
|
||||||
kafka:
|
kafka:
|
||||||
# Where the kafka servers are running.
|
# Where the kafka servers are running.
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
./scripts/build-test-lint.sh
|
./scripts/find-lint.sh fast
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
"Deadline": "5m",
|
"Deadline": "5m",
|
||||||
"Enable": [
|
"Enable": [
|
||||||
"vetshadow",
|
"vetshadow",
|
||||||
"gotype",
|
|
||||||
"deadcode",
|
"deadcode",
|
||||||
"gocyclo",
|
"gocyclo",
|
||||||
"ineffassign",
|
"ineffassign",
|
||||||
|
|
@ -12,6 +11,7 @@
|
||||||
"misspell",
|
"misspell",
|
||||||
"errcheck",
|
"errcheck",
|
||||||
"vet",
|
"vet",
|
||||||
|
"gofmt",
|
||||||
"goconst"
|
"goconst"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,12 @@
|
||||||
"Deadline": "5m",
|
"Deadline": "5m",
|
||||||
"Enable": [
|
"Enable": [
|
||||||
"vetshadow",
|
"vetshadow",
|
||||||
"gotype",
|
|
||||||
"deadcode",
|
"deadcode",
|
||||||
"gocyclo",
|
"gocyclo",
|
||||||
"golint",
|
"golint",
|
||||||
"varcheck",
|
"varcheck",
|
||||||
"structcheck",
|
"structcheck",
|
||||||
"aligncheck",
|
"maligned",
|
||||||
"ineffassign",
|
"ineffassign",
|
||||||
"gas",
|
"gas",
|
||||||
"misspell",
|
"misspell",
|
||||||
|
|
@ -18,6 +17,7 @@
|
||||||
"errcheck",
|
"errcheck",
|
||||||
"vet",
|
"vet",
|
||||||
"megacheck",
|
"megacheck",
|
||||||
|
"gofmt",
|
||||||
"goconst"
|
"goconst"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
export GOPATH="$(pwd):$(pwd)/vendor"
|
export GOPATH="$(pwd):$(pwd)/vendor"
|
||||||
export PATH="$PATH:$(pwd)/vendor/bin:$(pwd)/bin"
|
export PATH="$PATH:$(pwd)/bin"
|
||||||
|
|
||||||
echo "Checking that it builds"
|
echo "Checking that it builds"
|
||||||
gb build
|
gb build
|
||||||
|
|
@ -19,8 +19,5 @@ go build github.com/matrix-org/dendrite/cmd/...
|
||||||
|
|
||||||
./scripts/find-lint.sh
|
./scripts/find-lint.sh
|
||||||
|
|
||||||
echo "Double checking spelling..."
|
|
||||||
misspell -error src *.md
|
|
||||||
|
|
||||||
echo "Testing..."
|
echo "Testing..."
|
||||||
gb test
|
gb test
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
export GOPATH="$(pwd):$(pwd)/vendor"
|
export GOPATH="$(pwd):$(pwd)/vendor"
|
||||||
export PATH="$PATH:$(pwd)/vendor/bin:$(pwd)/bin"
|
export PATH="$PATH:$(pwd)/bin"
|
||||||
|
|
||||||
args=""
|
args=""
|
||||||
if [ ${1:-""} = "fast" ]
|
if [ ${1:-""} = "fast" ]
|
||||||
|
|
@ -31,7 +31,7 @@ then args="$args --enable-gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Installing lint search engine..."
|
echo "Installing lint search engine..."
|
||||||
go install github.com/alecthomas/gometalinter/
|
gb build github.com/alecthomas/gometalinter/
|
||||||
gometalinter --config=linter.json ./... --install
|
gometalinter --config=linter.json ./... --install
|
||||||
|
|
||||||
echo "Looking for lint..."
|
echo "Looking for lint..."
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
|
||||||
# The entry point for travis tests
|
# 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
|
set -eu
|
||||||
|
|
||||||
|
|
@ -8,20 +12,44 @@ set -eu
|
||||||
export GOGC=400
|
export GOGC=400
|
||||||
export DENDRITE_LINT_DISABLE_GC=1
|
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)
|
export GOPATH="$(pwd):$(pwd)/vendor"
|
||||||
gb build github.com/matrix-org/dendrite/cmd/dendrite-room-server
|
export PATH="$PATH:$(pwd)/bin"
|
||||||
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
|
|
||||||
|
|
||||||
# Run unit tests and linters
|
if [ "${TEST_SUITE:-lint}" == "lint" ]; then
|
||||||
./scripts/build-test-lint.sh
|
./scripts/find-lint.sh
|
||||||
|
fi
|
||||||
|
|
||||||
# Run the integration tests
|
if [ "${TEST_SUITE:-unit-test}" == "unit-test" ]; then
|
||||||
bin/roomserver-integration-tests
|
gb test
|
||||||
bin/syncserver-integration-tests
|
fi
|
||||||
bin/mediaapi-integration-tests
|
|
||||||
|
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"
|
"strings"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"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/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
@ -62,10 +63,8 @@ func VerifyAccessToken(req *http.Request, deviceDB DeviceDatabase) (device *auth
|
||||||
JSON: jsonerror.UnknownToken("Unknown token"),
|
JSON: jsonerror.UnknownToken("Unknown token"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resErr = &util.JSONResponse{
|
jsonErr := httputil.LogThenError(req, err)
|
||||||
Code: 500,
|
resErr = &jsonErr
|
||||||
JSON: jsonerror.Unknown("Failed to check access token"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ package accounts
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
const filterSchema = `
|
const filterSchema = `
|
||||||
|
|
@ -38,12 +40,16 @@ CREATE INDEX IF NOT EXISTS account_filter_localpart ON account_filter(localpart)
|
||||||
const selectFilterSQL = "" +
|
const selectFilterSQL = "" +
|
||||||
"SELECT filter FROM account_filter WHERE localpart = $1 AND id = $2"
|
"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 = "" +
|
const insertFilterSQL = "" +
|
||||||
"INSERT INTO account_filter (filter, id, localpart) VALUES ($1, DEFAULT, $2) RETURNING id"
|
"INSERT INTO account_filter (filter, id, localpart) VALUES ($1, DEFAULT, $2) RETURNING id"
|
||||||
|
|
||||||
type filterStatements struct {
|
type filterStatements struct {
|
||||||
selectFilterStmt *sql.Stmt
|
selectFilterStmt *sql.Stmt
|
||||||
insertFilterStmt *sql.Stmt
|
selectFilterIDByContentStmt *sql.Stmt
|
||||||
|
insertFilterStmt *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *filterStatements) prepare(db *sql.DB) (err error) {
|
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 {
|
if s.selectFilterStmt, err = db.Prepare(selectFilterSQL); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if s.selectFilterIDByContentStmt, err = db.Prepare(selectFilterIDByContentSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if s.insertFilterStmt, err = db.Prepare(insertFilterSQL); err != nil {
|
if s.insertFilterStmt, err = db.Prepare(insertFilterSQL); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -62,14 +71,37 @@ func (s *filterStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
|
||||||
func (s *filterStatements) selectFilter(
|
func (s *filterStatements) selectFilter(
|
||||||
ctx context.Context, localpart string, filterID string,
|
ctx context.Context, localpart string, filterID string,
|
||||||
) (filter string, err error) {
|
) (filter []byte, err error) {
|
||||||
err = s.selectFilterStmt.QueryRowContext(ctx, localpart, filterID).Scan(&filter)
|
err = s.selectFilterStmt.QueryRowContext(ctx, localpart, filterID).Scan(&filter)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *filterStatements) insertFilter(
|
func (s *filterStatements) insertFilter(
|
||||||
ctx context.Context, filter string, localpart string,
|
ctx context.Context, filter []byte, localpart string,
|
||||||
) (pos string, err error) {
|
) (filterID string, err error) {
|
||||||
err = s.insertFilterStmt.QueryRowContext(ctx, filter, localpart).Scan(&pos)
|
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 && err != sql.ErrNoRows {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// If it does, return the existing ID
|
||||||
|
if existingFilterID != "" {
|
||||||
|
return existingFilterID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise insert the filter and return the new ID
|
||||||
|
err = s.insertFilterStmt.QueryRowContext(ctx, filterJSON, localpart).
|
||||||
|
Scan(&filterID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -321,22 +321,25 @@ func (d *Database) GetThreePIDsForLocalpart(
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFilter looks up the filter associated with a given local user and filter ID.
|
// 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(
|
func (d *Database) GetFilter(
|
||||||
ctx context.Context, localpart string, filterID string,
|
ctx context.Context, localpart string, filterID string,
|
||||||
) (string, error) {
|
) ([]byte, error) {
|
||||||
return d.filter.selectFilter(ctx, localpart, filterID)
|
return d.filter.selectFilter(ctx, localpart, filterID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutFilter puts the passed filter into the database.
|
// 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(
|
func (d *Database) PutFilter(
|
||||||
ctx context.Context, localpart, filter string,
|
ctx context.Context, localpart string, filter []byte,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
return d.filter.insertFilter(ctx, filter, localpart)
|
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.
|
// If the DB returns sql.ErrNoRows the Localpart isn't taken.
|
||||||
func (d *Database) CheckAccountAvailability(ctx context.Context, localpart string) (bool, error) {
|
func (d *Database) CheckAccountAvailability(ctx context.Context, localpart string) (bool, error) {
|
||||||
_, err := d.accounts.selectAccountByLocalpart(ctx, localpart)
|
_, err := d.accounts.selectAccountByLocalpart(ctx, localpart)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ CREATE TABLE IF NOT EXISTS device_devices (
|
||||||
-- migration to different domain names easier.
|
-- migration to different domain names easier.
|
||||||
localpart TEXT NOT NULL,
|
localpart TEXT NOT NULL,
|
||||||
-- When this devices was first recognised on the network, as a unix timestamp (ms resolution).
|
-- 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)
|
-- TODO: device keys, device display names, last used ts and IP address?, token restrictions (if 3rd-party OAuth app)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -49,21 +51,35 @@ CREATE UNIQUE INDEX IF NOT EXISTS device_localpart_id_idx ON device_devices(loca
|
||||||
`
|
`
|
||||||
|
|
||||||
const insertDeviceSQL = "" +
|
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 = "" +
|
const selectDeviceByTokenSQL = "" +
|
||||||
"SELECT device_id, localpart FROM device_devices WHERE access_token = $1"
|
"SELECT device_id, localpart FROM device_devices WHERE access_token = $1"
|
||||||
|
|
||||||
|
const selectDeviceByIDSQL = "" +
|
||||||
|
"SELECT display_name FROM device_devices WHERE localpart = $1 and device_id = $2"
|
||||||
|
|
||||||
|
const selectDevicesByLocalpartSQL = "" +
|
||||||
|
"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 = "" +
|
const deleteDeviceSQL = "" +
|
||||||
"DELETE FROM device_devices WHERE device_id = $1 AND localpart = $2"
|
"DELETE FROM device_devices WHERE device_id = $1 AND localpart = $2"
|
||||||
|
|
||||||
// TODO: List devices?
|
const deleteDevicesByLocalpartSQL = "" +
|
||||||
|
"DELETE FROM device_devices WHERE localpart = $1"
|
||||||
|
|
||||||
type devicesStatements struct {
|
type devicesStatements struct {
|
||||||
insertDeviceStmt *sql.Stmt
|
insertDeviceStmt *sql.Stmt
|
||||||
selectDeviceByTokenStmt *sql.Stmt
|
selectDeviceByTokenStmt *sql.Stmt
|
||||||
deleteDeviceStmt *sql.Stmt
|
selectDeviceByIDStmt *sql.Stmt
|
||||||
serverName gomatrixserverlib.ServerName
|
selectDevicesByLocalpartStmt *sql.Stmt
|
||||||
|
updateDeviceNameStmt *sql.Stmt
|
||||||
|
deleteDeviceStmt *sql.Stmt
|
||||||
|
deleteDevicesByLocalpartStmt *sql.Stmt
|
||||||
|
serverName gomatrixserverlib.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
||||||
|
|
@ -77,9 +93,21 @@ func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerN
|
||||||
if s.selectDeviceByTokenStmt, err = db.Prepare(selectDeviceByTokenSQL); err != nil {
|
if s.selectDeviceByTokenStmt, err = db.Prepare(selectDeviceByTokenSQL); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if s.selectDeviceByIDStmt, err = db.Prepare(selectDeviceByIDSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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 {
|
if s.deleteDeviceStmt, err = db.Prepare(deleteDeviceSQL); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if s.deleteDevicesByLocalpartStmt, err = db.Prepare(deleteDevicesByLocalpartSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
s.serverName = server
|
s.serverName = server
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -89,10 +117,11 @@ func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerN
|
||||||
// Returns the device on success.
|
// Returns the device on success.
|
||||||
func (s *devicesStatements) insertDevice(
|
func (s *devicesStatements) insertDevice(
|
||||||
ctx context.Context, txn *sql.Tx, id, localpart, accessToken string,
|
ctx context.Context, txn *sql.Tx, id, localpart, accessToken string,
|
||||||
|
displayName *string,
|
||||||
) (*authtypes.Device, error) {
|
) (*authtypes.Device, error) {
|
||||||
createdTimeMS := time.Now().UnixNano() / 1000000
|
createdTimeMS := time.Now().UnixNano() / 1000000
|
||||||
stmt := common.TxStmt(txn, s.insertDeviceStmt)
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &authtypes.Device{
|
return &authtypes.Device{
|
||||||
|
|
@ -110,6 +139,22 @@ func (s *devicesStatements) deleteDevice(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) deleteDevicesByLocalpart(
|
||||||
|
ctx context.Context, txn *sql.Tx, localpart string,
|
||||||
|
) error {
|
||||||
|
stmt := common.TxStmt(txn, s.deleteDevicesByLocalpartStmt)
|
||||||
|
_, err := stmt.ExecContext(ctx, localpart)
|
||||||
|
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(
|
func (s *devicesStatements) selectDeviceByToken(
|
||||||
ctx context.Context, accessToken string,
|
ctx context.Context, accessToken string,
|
||||||
) (*authtypes.Device, error) {
|
) (*authtypes.Device, error) {
|
||||||
|
|
@ -124,6 +169,44 @@ func (s *devicesStatements) selectDeviceByToken(
|
||||||
return &dev, err
|
return &dev, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) selectDeviceByID(
|
||||||
|
ctx context.Context, localpart, deviceID string,
|
||||||
|
) (*authtypes.Device, error) {
|
||||||
|
var dev authtypes.Device
|
||||||
|
var created int64
|
||||||
|
stmt := s.selectDeviceByIDStmt
|
||||||
|
err := stmt.QueryRowContext(ctx, localpart, deviceID).Scan(&created)
|
||||||
|
if err == nil {
|
||||||
|
dev.ID = deviceID
|
||||||
|
dev.UserID = makeUserID(localpart, s.serverName)
|
||||||
|
}
|
||||||
|
return &dev, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) selectDevicesByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) ([]authtypes.Device, error) {
|
||||||
|
devices := []authtypes.Device{}
|
||||||
|
|
||||||
|
rows, err := s.selectDevicesByLocalpartStmt.QueryContext(ctx, localpart)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return devices, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var dev authtypes.Device
|
||||||
|
err = rows.Scan(&dev.ID)
|
||||||
|
if err != nil {
|
||||||
|
return devices, err
|
||||||
|
}
|
||||||
|
dev.UserID = makeUserID(localpart, s.serverName)
|
||||||
|
devices = append(devices, dev)
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices, nil
|
||||||
|
}
|
||||||
|
|
||||||
func makeUserID(localpart string, server gomatrixserverlib.ServerName) string {
|
func makeUserID(localpart string, server gomatrixserverlib.ServerName) string {
|
||||||
return fmt.Sprintf("@%s:%s", localpart, string(server))
|
return fmt.Sprintf("@%s:%s", localpart, string(server))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,21 @@ func (d *Database) GetDeviceByAccessToken(
|
||||||
return d.devices.selectDeviceByToken(ctx, token)
|
return d.devices.selectDeviceByToken(ctx, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDeviceByID returns the device matching the given ID.
|
||||||
|
// Returns sql.ErrNoRows if no matching device was found.
|
||||||
|
func (d *Database) GetDeviceByID(
|
||||||
|
ctx context.Context, localpart, deviceID string,
|
||||||
|
) (*authtypes.Device, error) {
|
||||||
|
return d.devices.selectDeviceByID(ctx, localpart, deviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDevicesByLocalpart returns the devices matching the given localpart.
|
||||||
|
func (d *Database) GetDevicesByLocalpart(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) ([]authtypes.Device, error) {
|
||||||
|
return d.devices.selectDevicesByLocalpart(ctx, localpart)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateDevice makes a new device associated with the given user ID localpart.
|
// CreateDevice makes a new device associated with the given user ID localpart.
|
||||||
// If there is already a device with the same device ID for this user, that access token will be revoked
|
// If there is already a device with the same device ID for this user, that access token will be revoked
|
||||||
// and replaced with the given accessToken. If the given accessToken is already in use for another device,
|
// and replaced with the given accessToken. If the given accessToken is already in use for another device,
|
||||||
|
|
@ -60,6 +75,7 @@ func (d *Database) GetDeviceByAccessToken(
|
||||||
// Returns the device on success.
|
// Returns the device on success.
|
||||||
func (d *Database) CreateDevice(
|
func (d *Database) CreateDevice(
|
||||||
ctx context.Context, localpart string, deviceID *string, accessToken string,
|
ctx context.Context, localpart string, deviceID *string, accessToken string,
|
||||||
|
displayName *string,
|
||||||
) (dev *authtypes.Device, returnErr error) {
|
) (dev *authtypes.Device, returnErr error) {
|
||||||
if deviceID != nil {
|
if deviceID != nil {
|
||||||
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
|
@ -69,7 +85,7 @@ func (d *Database) CreateDevice(
|
||||||
return err
|
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
|
return err
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -84,7 +100,7 @@ func (d *Database) CreateDevice(
|
||||||
|
|
||||||
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
returnErr = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
var err 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
|
return err
|
||||||
})
|
})
|
||||||
if returnErr == nil {
|
if returnErr == nil {
|
||||||
|
|
@ -95,6 +111,16 @@ func (d *Database) CreateDevice(
|
||||||
return
|
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
|
// RemoveDevice revokes a device by deleting the entry in the database
|
||||||
// matching with the given device ID and user ID localpart
|
// matching with the given device ID and user ID localpart
|
||||||
// If the device doesn't exist, it will not return an error
|
// If the device doesn't exist, it will not return an error
|
||||||
|
|
@ -109,3 +135,17 @@ func (d *Database) RemoveDevice(
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveAllDevices revokes devices by deleting the entry in the
|
||||||
|
// database matching the given user ID localpart.
|
||||||
|
// If something went wrong during the deletion, it will return the SQL error.
|
||||||
|
func (d *Database) RemoveAllDevices(
|
||||||
|
ctx context.Context, localpart string,
|
||||||
|
) error {
|
||||||
|
return common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
if err := d.devices.deleteDevicesByLocalpart(ctx, txn, localpart); err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,11 +96,7 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.db.UpdateMemberships(context.TODO(), events, output.NewRoomEvent.RemovesStateEventIDs); err != nil {
|
return s.db.UpdateMemberships(context.TODO(), events, output.NewRoomEvent.RemovesStateEventIDs)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookupStateEvents looks up the state events that are added by a new event.
|
// 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}
|
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
|
// MissingToken is an error when the client tries to access a resource which
|
||||||
// requires authentication without supplying credentials.
|
// requires authentication without supplying credentials.
|
||||||
func MissingToken(msg string) *MatrixError {
|
func MissingToken(msg string) *MatrixError {
|
||||||
|
|
|
||||||
155
src/github.com/matrix-org/dendrite/clientapi/routing/device.go
Normal file
155
src/github.com/matrix-org/dendrite/clientapi/routing/device.go
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright 2017 Paul Tötterman <paul.totterman@iki.fi>
|
||||||
|
//
|
||||||
|
// 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 (
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type deviceJSON struct {
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
deviceID string,
|
||||||
|
) util.JSONResponse {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: deviceJSON{
|
||||||
|
DeviceID: dev.ID,
|
||||||
|
UserID: dev.UserID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDevicesByLocalpart handles /devices
|
||||||
|
func GetDevicesByLocalpart(
|
||||||
|
req *http.Request, deviceDB *devices.Database, device *authtypes.Device,
|
||||||
|
) util.JSONResponse {
|
||||||
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := req.Context()
|
||||||
|
deviceList, err := deviceDB.GetDevicesByLocalpart(ctx, localpart)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := devicesJSON{}
|
||||||
|
|
||||||
|
for _, dev := range deviceList {
|
||||||
|
res.Devices = append(res.Devices, deviceJSON{
|
||||||
|
DeviceID: dev.ID,
|
||||||
|
UserID: dev.UserID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
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{}
|
filter := gomatrix.Filter{}
|
||||||
err = json.Unmarshal([]byte(res), &filter)
|
err = json.Unmarshal(res, &filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httputil.LogThenError(req, err)
|
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 {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
return httputil.LogThenError(req, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
"github.com/matrix-org/dendrite/clientapi/events"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
|
|
@ -215,7 +215,7 @@ func (r joinRoomReq) joinRoomUsingServers(
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryRes api.QueryLatestEventsAndStateResponse
|
var queryRes api.QueryLatestEventsAndStateResponse
|
||||||
event, err := events.BuildEvent(r.req.Context(), &eb, r.cfg, r.queryAPI, &queryRes)
|
event, err := common.BuildEvent(r.req.Context(), &eb, r.cfg, r.queryAPI, &queryRes)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err = r.producer.SendEvents(r.req.Context(), []gomatrixserverlib.Event{*event}, r.cfg.Matrix.ServerName); err != nil {
|
if err = r.producer.SendEvents(r.req.Context(), []gomatrixserverlib.Event{*event}, r.cfg.Matrix.ServerName); err != nil {
|
||||||
return httputil.LogThenError(r.req, err)
|
return httputil.LogThenError(r.req, err)
|
||||||
|
|
@ -227,7 +227,7 @@ func (r joinRoomReq) joinRoomUsingServers(
|
||||||
}{roomID},
|
}{roomID},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != events.ErrRoomNoExists {
|
if err != common.ErrRoomNoExists {
|
||||||
return httputil.LogThenError(r.req, err)
|
return httputil.LogThenError(r.req, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,9 @@ type flow struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type passwordRequest struct {
|
type passwordRequest struct {
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
InitialDisplayName *string `json:"initial_device_display_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type loginResponse struct {
|
type loginResponse struct {
|
||||||
|
|
@ -119,7 +120,7 @@ func Login(
|
||||||
|
|
||||||
// TODO: Use the device ID in the request
|
// TODO: Use the device ID in the request
|
||||||
dev, err := deviceDB.CreateDevice(
|
dev, err := deviceDB.CreateDevice(
|
||||||
req.Context(), acc.Localpart, nil, token,
|
req.Context(), acc.Localpart, nil, token, r.InitialDisplayName,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -50,3 +50,22 @@ func Logout(
|
||||||
JSON: struct{}{},
|
JSON: struct{}{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogoutAll handles POST /logout/all
|
||||||
|
func LogoutAll(
|
||||||
|
req *http.Request, deviceDB *devices.Database, device *authtypes.Device,
|
||||||
|
) util.JSONResponse {
|
||||||
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deviceDB.RemoveAllDevices(req.Context(), localpart); err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
"github.com/matrix-org/dendrite/clientapi/events"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
|
|
@ -62,7 +61,7 @@ func SendMembership(
|
||||||
Code: 400,
|
Code: 400,
|
||||||
JSON: jsonerror.NotTrusted(body.IDServer),
|
JSON: jsonerror.NotTrusted(body.IDServer),
|
||||||
}
|
}
|
||||||
} else if err == events.ErrRoomNoExists {
|
} else if err == common.ErrRoomNoExists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
JSON: jsonerror.NotFound(err.Error()),
|
JSON: jsonerror.NotFound(err.Error()),
|
||||||
|
|
@ -89,7 +88,7 @@ func SendMembership(
|
||||||
Code: 400,
|
Code: 400,
|
||||||
JSON: jsonerror.BadJSON(err.Error()),
|
JSON: jsonerror.BadJSON(err.Error()),
|
||||||
}
|
}
|
||||||
} else if err == events.ErrRoomNoExists {
|
} else if err == common.ErrRoomNoExists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
JSON: jsonerror.NotFound(err.Error()),
|
JSON: jsonerror.NotFound(err.Error()),
|
||||||
|
|
@ -149,7 +148,7 @@ func buildMembershipEvent(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return events.BuildEvent(ctx, &builder, cfg, queryAPI, nil)
|
return common.BuildEvent(ctx, &builder, cfg, queryAPI, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadProfile lookups the profile of a given user from the database and returns
|
// loadProfile lookups the profile of a given user from the database and returns
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ type response struct {
|
||||||
// GetMemberships implements GET /rooms/{roomId}/members
|
// GetMemberships implements GET /rooms/{roomId}/members
|
||||||
func GetMemberships(
|
func GetMemberships(
|
||||||
req *http.Request, device *authtypes.Device, roomID string, joinedOnly bool,
|
req *http.Request, device *authtypes.Device, roomID string, joinedOnly bool,
|
||||||
cfg config.Dendrite,
|
_ config.Dendrite,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
queryReq := api.QueryMembershipsForRoomRequest{
|
queryReq := api.QueryMembershipsForRoomRequest{
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
"github.com/matrix-org/dendrite/clientapi/events"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
|
|
@ -32,19 +31,6 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"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}
|
// GetProfile implements GET /profile/{userID}
|
||||||
func GetProfile(
|
func GetProfile(
|
||||||
req *http.Request, accountDB *accounts.Database, userID string,
|
req *http.Request, accountDB *accounts.Database, userID string,
|
||||||
|
|
@ -64,7 +50,7 @@ func GetProfile(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
return httputil.LogThenError(req, err)
|
||||||
}
|
}
|
||||||
res := profileResponse{
|
res := common.ProfileResponse{
|
||||||
AvatarURL: profile.AvatarURL,
|
AvatarURL: profile.AvatarURL,
|
||||||
DisplayName: profile.DisplayName,
|
DisplayName: profile.DisplayName,
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +73,7 @@ func GetAvatarURL(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
return httputil.LogThenError(req, err)
|
||||||
}
|
}
|
||||||
res := avatarURL{
|
res := common.AvatarURL{
|
||||||
AvatarURL: profile.AvatarURL,
|
AvatarURL: profile.AvatarURL,
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -111,7 +97,7 @@ func SetAvatarURL(
|
||||||
|
|
||||||
changedKey := "avatar_url"
|
changedKey := "avatar_url"
|
||||||
|
|
||||||
var r avatarURL
|
var r common.AvatarURL
|
||||||
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +165,7 @@ func GetDisplayName(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(req, err)
|
return httputil.LogThenError(req, err)
|
||||||
}
|
}
|
||||||
res := displayName{
|
res := common.DisplayName{
|
||||||
DisplayName: profile.DisplayName,
|
DisplayName: profile.DisplayName,
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -203,7 +189,7 @@ func SetDisplayName(
|
||||||
|
|
||||||
changedKey := "displayname"
|
changedKey := "displayname"
|
||||||
|
|
||||||
var r displayName
|
var r common.DisplayName
|
||||||
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
@ -285,7 +271,7 @@ func buildMembershipEvents(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
event, err := events.BuildEvent(ctx, &builder, *cfg, queryAPI, nil)
|
event, err := common.BuildEvent(ctx, &builder, *cfg, queryAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"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"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"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/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -60,6 +60,8 @@ type registerRequest struct {
|
||||||
Admin bool `json:"admin"`
|
Admin bool `json:"admin"`
|
||||||
// user-interactive auth params
|
// user-interactive auth params
|
||||||
Auth authDict `json:"auth"`
|
Auth authDict `json:"auth"`
|
||||||
|
|
||||||
|
InitialDisplayName *string `json:"initial_device_display_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type authDict struct {
|
type authDict struct {
|
||||||
|
|
@ -210,10 +212,10 @@ func Register(
|
||||||
return util.MessageResponse(403, "HMAC incorrect")
|
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:
|
case authtypes.LoginTypeDummy:
|
||||||
// there is nothing to do
|
// 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:
|
default:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 501,
|
Code: 501,
|
||||||
|
|
@ -270,10 +272,10 @@ func LegacyRegister(
|
||||||
return util.MessageResponse(403, "HMAC incorrect")
|
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:
|
case authtypes.LoginTypeDummy:
|
||||||
// there is nothing to do
|
// 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:
|
default:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 501,
|
Code: 501,
|
||||||
|
|
@ -287,6 +289,7 @@ func completeRegistration(
|
||||||
accountDB *accounts.Database,
|
accountDB *accounts.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB *devices.Database,
|
||||||
username, password string,
|
username, password string,
|
||||||
|
displayName *string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -318,7 +321,7 @@ func completeRegistration(
|
||||||
}
|
}
|
||||||
|
|
||||||
// // TODO: Use the device ID in the request.
|
// // 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 {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 500,
|
Code: 500,
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,12 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods("POST", "OPTIONS")
|
).Methods("POST", "OPTIONS")
|
||||||
|
|
||||||
|
r0mux.Handle("/logout/all",
|
||||||
|
common.MakeAuthAPI("logout", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
return LogoutAll(req, deviceDB, device)
|
||||||
|
}),
|
||||||
|
).Methods("POST", "OPTIONS")
|
||||||
|
|
||||||
// Stub endpoints required by Riot
|
// Stub endpoints required by Riot
|
||||||
|
|
||||||
r0mux.Handle("/login",
|
r0mux.Handle("/login",
|
||||||
|
|
@ -278,12 +284,8 @@ func Setup(
|
||||||
).Methods("PUT", "OPTIONS")
|
).Methods("PUT", "OPTIONS")
|
||||||
|
|
||||||
r0mux.Handle("/voip/turnServer",
|
r0mux.Handle("/voip/turnServer",
|
||||||
common.MakeExternalAPI("turn_server", func(req *http.Request) util.JSONResponse {
|
common.MakeAuthAPI("turn_server", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
// TODO: Return credentials for a turn server if one is configured.
|
return RequestTurnServer(req, device, cfg)
|
||||||
return util.JSONResponse{
|
|
||||||
Code: 200,
|
|
||||||
JSON: struct{}{},
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
).Methods("GET")
|
).Methods("GET")
|
||||||
|
|
||||||
|
|
@ -349,6 +351,26 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods("PUT", "OPTIONS")
|
).Methods("PUT", "OPTIONS")
|
||||||
|
|
||||||
|
r0mux.Handle("/devices",
|
||||||
|
common.MakeAuthAPI("get_devices", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
return GetDevicesByLocalpart(req, deviceDB, device)
|
||||||
|
}),
|
||||||
|
).Methods("GET")
|
||||||
|
|
||||||
|
r0mux.Handle("/device/{deviceID}",
|
||||||
|
common.MakeAuthAPI("get_device", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
return GetDeviceByID(req, deviceDB, device, vars["deviceID"])
|
||||||
|
}),
|
||||||
|
).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
|
// Stub implementations for sytest
|
||||||
r0mux.Handle("/events",
|
r0mux.Handle("/events",
|
||||||
common.MakeExternalAPI("events", func(req *http.Request) util.JSONResponse {
|
common.MakeExternalAPI("events", func(req *http.Request) util.JSONResponse {
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/events"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
@ -67,8 +67,8 @@ func SendEvent(
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryRes api.QueryLatestEventsAndStateResponse
|
var queryRes api.QueryLatestEventsAndStateResponse
|
||||||
e, err := events.BuildEvent(req.Context(), &builder, cfg, queryAPI, &queryRes)
|
e, err := common.BuildEvent(req.Context(), &builder, cfg, queryAPI, &queryRes)
|
||||||
if err == events.ErrRoomNoExists {
|
if err == common.ErrRoomNoExists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
JSON: jsonerror.NotFound("Room does not exist"),
|
JSON: jsonerror.NotFound("Room does not exist"),
|
||||||
|
|
|
||||||
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,7 +26,6 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
"github.com/matrix-org/dendrite/clientapi/events"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
|
@ -351,14 +350,10 @@ func emit3PIDInviteEvent(
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryRes *api.QueryLatestEventsAndStateResponse
|
var queryRes *api.QueryLatestEventsAndStateResponse
|
||||||
event, err := events.BuildEvent(ctx, builder, cfg, queryAPI, queryRes)
|
event, err := common.BuildEvent(ctx, builder, cfg, queryAPI, queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := producer.SendEvents(ctx, []gomatrixserverlib.Event{*event}, cfg.Matrix.ServerName); err != nil {
|
return producer.SendEvents(ctx, []gomatrixserverlib.Event{*event}, cfg.Matrix.ServerName)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
device, err := deviceDB.CreateDevice(
|
device, err := deviceDB.CreateDevice(
|
||||||
context.Background(), *username, nil, *accessToken,
|
context.Background(), *username, nil, *accessToken, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
|
|
|
||||||
|
|
@ -98,13 +98,7 @@ func main() {
|
||||||
log.Panicf("Failed to setup key database(%q): %s", cfg.Database.ServerKey, err.Error())
|
log.Panicf("Failed to setup key database(%q): %s", cfg.Database.ServerKey, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
keyRing := gomatrixserverlib.KeyRing{
|
keyRing := keydb.CreateKeyRing(federation.Client, keyDB)
|
||||||
KeyFetchers: []gomatrixserverlib.KeyFetcher{
|
|
||||||
// TODO: Use perspective key fetchers for production.
|
|
||||||
&gomatrixserverlib.DirectKeyFetcher{Client: federation.Client},
|
|
||||||
},
|
|
||||||
KeyDatabase: keyDB,
|
|
||||||
}
|
|
||||||
|
|
||||||
kafkaConsumer, err := sarama.NewConsumer(cfg.Kafka.Addresses, nil)
|
kafkaConsumer, err := sarama.NewConsumer(cfg.Kafka.Addresses, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ func main() {
|
||||||
|
|
||||||
queryAPI := api.NewRoomserverQueryAPIHTTP(cfg.RoomServerURL(), nil)
|
queryAPI := api.NewRoomserverQueryAPIHTTP(cfg.RoomServerURL(), nil)
|
||||||
inputAPI := api.NewRoomserverInputAPIHTTP(cfg.RoomServerURL(), nil)
|
inputAPI := api.NewRoomserverInputAPIHTTP(cfg.RoomServerURL(), nil)
|
||||||
|
aliasAPI := api.NewRoomserverAliasAPIHTTP(cfg.RoomServerURL(), nil)
|
||||||
|
|
||||||
roomserverProducer := producers.NewRoomserverProducer(inputAPI)
|
roomserverProducer := producers.NewRoomserverProducer(inputAPI)
|
||||||
|
|
||||||
|
|
@ -90,7 +91,7 @@ func main() {
|
||||||
log.Info("Starting federation API server on ", cfg.Listen.FederationAPI)
|
log.Info("Starting federation API server on ", cfg.Listen.FederationAPI)
|
||||||
|
|
||||||
api := mux.NewRouter()
|
api := mux.NewRouter()
|
||||||
routing.Setup(api, *cfg, queryAPI, roomserverProducer, keyRing, federation, accountDB)
|
routing.Setup(api, *cfg, queryAPI, aliasAPI, roomserverProducer, keyRing, federation, accountDB)
|
||||||
common.SetupHTTPAPI(http.DefaultServeMux, api)
|
common.SetupHTTPAPI(http.DefaultServeMux, api)
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe(string(cfg.Listen.FederationAPI), nil))
|
log.Fatal(http.ListenAndServe(string(cfg.Listen.FederationAPI), nil))
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"flag"
|
"flag"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -194,18 +195,26 @@ func (m *monolith) setupFederation() {
|
||||||
m.cfg.Matrix.ServerName, m.cfg.Matrix.KeyID, m.cfg.Matrix.PrivateKey,
|
m.cfg.Matrix.ServerName, m.cfg.Matrix.KeyID, m.cfg.Matrix.PrivateKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
m.keyRing = gomatrixserverlib.KeyRing{
|
m.keyRing = keydb.CreateKeyRing(m.federation.Client, m.keyDB)
|
||||||
KeyFetchers: []gomatrixserverlib.KeyFetcher{
|
|
||||||
// TODO: Use perspective key fetchers for production.
|
|
||||||
&gomatrixserverlib.DirectKeyFetcher{Client: m.federation.Client},
|
|
||||||
},
|
|
||||||
KeyDatabase: m.keyDB,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *monolith) setupKafka() {
|
func (m *monolith) setupKafka() {
|
||||||
if m.cfg.Kafka.UseNaffka {
|
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 {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
log.ErrorKey: err,
|
log.ErrorKey: err,
|
||||||
|
|
@ -336,10 +345,10 @@ func (m *monolith) setupAPIs() {
|
||||||
|
|
||||||
syncapi_routing.Setup(m.api, syncapi_sync.NewRequestPool(
|
syncapi_routing.Setup(m.api, syncapi_sync.NewRequestPool(
|
||||||
m.syncAPIDB, m.syncAPINotifier, m.accountDB,
|
m.syncAPIDB, m.syncAPINotifier, m.accountDB,
|
||||||
), m.deviceDB)
|
), m.syncAPIDB, m.deviceDB)
|
||||||
|
|
||||||
federationapi_routing.Setup(
|
federationapi_routing.Setup(
|
||||||
m.api, *m.cfg, m.queryAPI, m.roomServerProducer, m.keyRing, m.federation,
|
m.api, *m.cfg, m.queryAPI, m.aliasAPI, m.roomServerProducer, m.keyRing, m.federation,
|
||||||
m.accountDB,
|
m.accountDB,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ func main() {
|
||||||
log.Info("Starting sync server on ", cfg.Listen.SyncAPI)
|
log.Info("Starting sync server on ", cfg.Listen.SyncAPI)
|
||||||
|
|
||||||
api := mux.NewRouter()
|
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)
|
common.SetupHTTPAPI(http.DefaultServeMux, api)
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe(string(cfg.Listen.SyncAPI), nil))
|
log.Fatal(http.ListenAndServe(string(cfg.Listen.SyncAPI), nil))
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ var timeout time.Duration
|
||||||
|
|
||||||
var port = 10000
|
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)
|
dir, err := ioutil.TempDir("", serverType+"-server-test"+suffix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -107,7 +107,7 @@ func startMediaAPI(suffix string, dynamicThumbnails bool) (*exec.Cmd, chan error
|
||||||
testDatabaseName + suffix,
|
testDatabaseName + suffix,
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyCmd, proxyCmdChan := test.StartProxy(proxyAddr, cfg)
|
proxyCmd, _ := test.StartProxy(proxyAddr, cfg)
|
||||||
|
|
||||||
test.InitDatabase(
|
test.InitDatabase(
|
||||||
postgresDatabase,
|
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)
|
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) {
|
func cleanUpServer(cmd *exec.Cmd, dir string) {
|
||||||
|
|
@ -145,7 +145,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create server1 with only pre-generated thumbnails allowed
|
// 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 cleanUpServer(server1Cmd, server1Dir)
|
||||||
defer server1ProxyCmd.Process.Kill() // nolint: errcheck
|
defer server1ProxyCmd.Process.Kill() // nolint: errcheck
|
||||||
testDownload(server1ProxyAddr, server1ProxyAddr, "doesnotexist", 404, server1CmdChan)
|
testDownload(server1ProxyAddr, server1ProxyAddr, "doesnotexist", 404, server1CmdChan)
|
||||||
|
|
@ -162,7 +162,7 @@ func main() {
|
||||||
testThumbnail(64, 64, "crop", server1ProxyAddr, server1CmdChan)
|
testThumbnail(64, 64, "crop", server1ProxyAddr, server1CmdChan)
|
||||||
|
|
||||||
// create server2 with dynamic thumbnail generation
|
// 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 cleanUpServer(server2Cmd, server2Dir)
|
||||||
defer server2ProxyCmd.Process.Kill() // nolint: errcheck
|
defer server2ProxyCmd.Process.Kill() // nolint: errcheck
|
||||||
testDownload(server2ProxyAddr, server2ProxyAddr, "doesnotexist", 404, server2CmdChan)
|
testDownload(server2ProxyAddr, server2ProxyAddr, "doesnotexist", 404, server2CmdChan)
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
|
@ -148,8 +148,30 @@ type Dendrite struct {
|
||||||
// The PublicRoomsAPI database stores information used to compute the public
|
// The PublicRoomsAPI database stores information used to compute the public
|
||||||
// room directory. It is only accessed by the PublicRoomsAPI server.
|
// room directory. It is only accessed by the PublicRoomsAPI server.
|
||||||
PublicRoomsAPI DataSource `yaml:"public_rooms_api"`
|
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"`
|
} `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.
|
// The internal addresses the components will listen on.
|
||||||
// These should not be exposed externally as they expose metrics and debugging APIs.
|
// These should not be exposed externally as they expose metrics and debugging APIs.
|
||||||
Listen struct {
|
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.server_name", string(config.Matrix.ServerName))
|
||||||
checkNotEmpty("matrix.private_key", string(config.Matrix.PrivateKeyPath))
|
checkNotEmpty("matrix.private_key", string(config.Matrix.PrivateKeyPath))
|
||||||
checkNotZero("matrix.federation_certificates", int64(len(config.Matrix.FederationCertificatePaths)))
|
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))
|
checkNotEmpty("media.base_path", string(config.Media.BasePath))
|
||||||
checkPositive("media.max_file_size_bytes", int64(*config.Media.MaxFileSizeBytes))
|
checkPositive("media.max_file_size_bytes", int64(*config.Media.MaxFileSizeBytes))
|
||||||
checkPositive("media.max_thumbnail_generators", int64(config.Media.MaxThumbnailGenerators))
|
checkPositive("media.max_thumbnail_generators", int64(config.Media.MaxThumbnailGenerators))
|
||||||
|
|
@ -356,6 +388,8 @@ func (config *Dendrite) check(monolithic bool) error {
|
||||||
if !monolithic {
|
if !monolithic {
|
||||||
problems = append(problems, fmt.Sprintf("naffka can only be used in a monolithic server"))
|
problems = append(problems, fmt.Sprintf("naffka can only be used in a monolithic server"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkNotEmpty("database.naffka", string(config.Database.Naffka))
|
||||||
} else {
|
} else {
|
||||||
// If we aren't using naffka then we need to have at least one kafka
|
// If we aren't using naffka then we need to have at least one kafka
|
||||||
// server to talk to.
|
// server to talk to.
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package events
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -48,14 +48,14 @@ func NewDatabase(dataSourceName string) (*Database, error) {
|
||||||
func (d *Database) FetchKeys(
|
func (d *Database) FetchKeys(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
requests map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.Timestamp,
|
requests map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.Timestamp,
|
||||||
) (map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.ServerKeys, error) {
|
) (map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.PublicKeyLookupResult, error) {
|
||||||
return d.statements.bulkSelectServerKeys(ctx, requests)
|
return d.statements.bulkSelectServerKeys(ctx, requests)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StoreKeys implements gomatrixserverlib.KeyDatabase
|
// StoreKeys implements gomatrixserverlib.KeyDatabase
|
||||||
func (d *Database) StoreKeys(
|
func (d *Database) StoreKeys(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
keyMap map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.ServerKeys,
|
keyMap map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.PublicKeyLookupResult,
|
||||||
) error {
|
) error {
|
||||||
// TODO: Inserting all the keys within a single transaction may
|
// TODO: Inserting all the keys within a single transaction may
|
||||||
// be more efficient since the transaction overhead can be quite
|
// 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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
const serverKeysSchema = `
|
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 (
|
CREATE TABLE IF NOT EXISTS keydb_server_keys (
|
||||||
-- The name of the matrix server the key is for.
|
-- The name of the matrix server the key is for.
|
||||||
server_name TEXT NOT NULL,
|
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
|
-- Combined server name and key ID separated by the ASCII unit separator
|
||||||
-- to make it easier to run bulk queries.
|
-- to make it easier to run bulk queries.
|
||||||
server_name_and_key_id TEXT NOT NULL,
|
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,
|
valid_until_ts BIGINT NOT NULL,
|
||||||
-- The raw JSON for the server key.
|
-- When the key expired as a millisecond timestamp.
|
||||||
server_key_json TEXT NOT NULL,
|
-- 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)
|
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 = "" +
|
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)"
|
" WHERE server_name_and_key_id = ANY($1)"
|
||||||
|
|
||||||
const upsertServerKeysSQL = "" +
|
const upsertServerKeysSQL = "" +
|
||||||
"INSERT INTO keydb_server_keys (server_name, server_key_id," +
|
"INSERT INTO keydb_server_keys (server_name, server_key_id," +
|
||||||
" server_name_and_key_id, valid_until_ts, server_key_json)" +
|
" server_name_and_key_id, valid_until_ts, expired_ts, server_key)" +
|
||||||
" VALUES ($1, $2, $3, $4, $5)" +
|
" VALUES ($1, $2, $3, $4, $5, $6)" +
|
||||||
" ON CONFLICT ON CONSTRAINT keydb_server_keys_unique" +
|
" 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 {
|
type serverKeyStatements struct {
|
||||||
bulkSelectServerKeysStmt *sql.Stmt
|
bulkSelectServerKeysStmt *sql.Stmt
|
||||||
|
|
@ -76,7 +80,7 @@ func (s *serverKeyStatements) prepare(db *sql.DB) (err error) {
|
||||||
func (s *serverKeyStatements) bulkSelectServerKeys(
|
func (s *serverKeyStatements) bulkSelectServerKeys(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
requests map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.Timestamp,
|
requests map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.Timestamp,
|
||||||
) (map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.ServerKeys, error) {
|
) (map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.PublicKeyLookupResult, error) {
|
||||||
var nameAndKeyIDs []string
|
var nameAndKeyIDs []string
|
||||||
for request := range requests {
|
for request := range requests {
|
||||||
nameAndKeyIDs = append(nameAndKeyIDs, nameAndKeyID(request))
|
nameAndKeyIDs = append(nameAndKeyIDs, nameAndKeyID(request))
|
||||||
|
|
@ -87,23 +91,30 @@ func (s *serverKeyStatements) bulkSelectServerKeys(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close() // nolint: errcheck
|
defer rows.Close() // nolint: errcheck
|
||||||
results := map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.ServerKeys{}
|
results := map[gomatrixserverlib.PublicKeyRequest]gomatrixserverlib.PublicKeyLookupResult{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var serverName string
|
var serverName string
|
||||||
var keyID string
|
var keyID string
|
||||||
var keyJSON []byte
|
var key string
|
||||||
if err := rows.Scan(&serverName, &keyID, &keyJSON); err != nil {
|
var validUntilTS int64
|
||||||
return nil, err
|
var expiredTS int64
|
||||||
}
|
if err = rows.Scan(&serverName, &keyID, &validUntilTS, &expiredTS, &key); err != nil {
|
||||||
var serverKeys gomatrixserverlib.ServerKeys
|
|
||||||
if err := json.Unmarshal(keyJSON, &serverKeys); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
r := gomatrixserverlib.PublicKeyRequest{
|
r := gomatrixserverlib.PublicKeyRequest{
|
||||||
ServerName: gomatrixserverlib.ServerName(serverName),
|
ServerName: gomatrixserverlib.ServerName(serverName),
|
||||||
KeyID: gomatrixserverlib.KeyID(keyID),
|
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
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
@ -111,19 +122,16 @@ func (s *serverKeyStatements) bulkSelectServerKeys(
|
||||||
func (s *serverKeyStatements) upsertServerKeys(
|
func (s *serverKeyStatements) upsertServerKeys(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request gomatrixserverlib.PublicKeyRequest,
|
request gomatrixserverlib.PublicKeyRequest,
|
||||||
keys gomatrixserverlib.ServerKeys,
|
key gomatrixserverlib.PublicKeyLookupResult,
|
||||||
) error {
|
) error {
|
||||||
keyJSON, err := json.Marshal(keys)
|
_, err := s.upsertServerKeysStmt.ExecContext(
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = s.upsertServerKeysStmt.ExecContext(
|
|
||||||
ctx,
|
ctx,
|
||||||
string(request.ServerName),
|
string(request.ServerName),
|
||||||
string(request.KeyID),
|
string(request.KeyID),
|
||||||
nameAndKeyID(request),
|
nameAndKeyID(request),
|
||||||
int64(keys.ValidUntilTS),
|
key.ValidUntilTS,
|
||||||
keyJSON,
|
key.ExpiredTS,
|
||||||
|
key.Key.Encode(),
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dugong"
|
"github.com/matrix-org/dugong"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type utcFormatter struct {
|
type utcFormatter struct {
|
||||||
|
|
|
||||||
|
|
@ -113,10 +113,7 @@ func WriteConfig(cfg *config.Dendrite, configDir string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = ioutil.WriteFile(filepath.Join(configDir, ConfigFile), data, 0666); err != nil {
|
return ioutil.WriteFile(filepath.Join(configDir, ConfigFile), data, 0666)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMatrixKey generates a new ed25519 matrix server key and writes it to a file.
|
// 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"`
|
RoomID string `json:"room_id"`
|
||||||
Type string `json:"type"`
|
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"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package readers
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -28,10 +28,10 @@ import (
|
||||||
func GetEvent(
|
func GetEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request *gomatrixserverlib.FederationRequest,
|
request *gomatrixserverlib.FederationRequest,
|
||||||
cfg config.Dendrite,
|
_ config.Dendrite,
|
||||||
query api.RoomserverQueryAPI,
|
query api.RoomserverQueryAPI,
|
||||||
now time.Time,
|
_ time.Time,
|
||||||
keys gomatrixserverlib.KeyRing,
|
_ gomatrixserverlib.KeyRing,
|
||||||
eventID string,
|
eventID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
var authResponse api.QueryServerAllowedToSeeEventResponse
|
var authResponse api.QueryServerAllowedToSeeEventResponse
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package readers
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
@ -38,7 +38,6 @@ func localKeys(cfg config.Dendrite, validUntil time.Time) (*gomatrixserverlib.Se
|
||||||
var keys gomatrixserverlib.ServerKeys
|
var keys gomatrixserverlib.ServerKeys
|
||||||
|
|
||||||
keys.ServerName = cfg.Matrix.ServerName
|
keys.ServerName = cfg.Matrix.ServerName
|
||||||
keys.FromServer = cfg.Matrix.ServerName
|
|
||||||
|
|
||||||
publicKey := cfg.Matrix.PrivateKey.Public().(ed25519.PublicKey)
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/gomatrix"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RoomAliasToID converts the queried alias into a room ID and returns it
|
||||||
|
func RoomAliasToID(
|
||||||
|
httpReq *http.Request,
|
||||||
|
federation *gomatrixserverlib.FederationClient,
|
||||||
|
cfg config.Dendrite,
|
||||||
|
aliasAPI api.RoomserverAliasAPI,
|
||||||
|
) util.JSONResponse {
|
||||||
|
roomAlias := httpReq.FormValue("alias")
|
||||||
|
if roomAlias == "" {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 400,
|
||||||
|
JSON: jsonerror.BadJSON("Must supply room alias parameter."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, domain, err := gomatrixserverlib.SplitID('#', roomAlias)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 400,
|
||||||
|
JSON: jsonerror.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp gomatrixserverlib.RespDirectory
|
||||||
|
|
||||||
|
if domain == cfg.Matrix.ServerName {
|
||||||
|
queryReq := api.GetAliasRoomIDRequest{Alias: roomAlias}
|
||||||
|
var queryRes api.GetAliasRoomIDResponse
|
||||||
|
if err = aliasAPI.GetAliasRoomID(httpReq.Context(), &queryReq, &queryRes); err != nil {
|
||||||
|
return httputil.LogThenError(httpReq, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryRes.RoomID == "" {
|
||||||
|
// TODO: List servers that are aware of this room alias
|
||||||
|
resp = gomatrixserverlib.RespDirectory{
|
||||||
|
RoomID: queryRes.RoomID,
|
||||||
|
Servers: []gomatrixserverlib.ServerName{},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the response doesn't contain a non-empty string, return an error
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 404,
|
||||||
|
JSON: jsonerror.NotFound(fmt.Sprintf("Room alias %s not found", roomAlias)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resp, err = federation.LookupRoomAlias(httpReq.Context(), domain, roomAlias)
|
||||||
|
if err != nil {
|
||||||
|
switch x := err.(type) {
|
||||||
|
case gomatrix.HTTPError:
|
||||||
|
if x.Code == 404 {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 404,
|
||||||
|
JSON: jsonerror.NotFound("Room alias not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Return 502 if the remote server errored.
|
||||||
|
// TODO: Return 504 if the remote server timed out.
|
||||||
|
return httputil.LogThenError(httpReq, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: resp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/federationapi/readers"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -39,6 +38,7 @@ func Setup(
|
||||||
apiMux *mux.Router,
|
apiMux *mux.Router,
|
||||||
cfg config.Dendrite,
|
cfg config.Dendrite,
|
||||||
query api.RoomserverQueryAPI,
|
query api.RoomserverQueryAPI,
|
||||||
|
aliasAPI api.RoomserverAliasAPI,
|
||||||
producer *producers.RoomserverProducer,
|
producer *producers.RoomserverProducer,
|
||||||
keys gomatrixserverlib.KeyRing,
|
keys gomatrixserverlib.KeyRing,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
|
|
@ -48,7 +48,7 @@ func Setup(
|
||||||
v1fedmux := apiMux.PathPrefix(pathPrefixV1Federation).Subrouter()
|
v1fedmux := apiMux.PathPrefix(pathPrefixV1Federation).Subrouter()
|
||||||
|
|
||||||
localKeys := common.MakeExternalAPI("localkeys", func(req *http.Request) util.JSONResponse {
|
localKeys := common.MakeExternalAPI("localkeys", func(req *http.Request) util.JSONResponse {
|
||||||
return readers.LocalKeys(cfg)
|
return LocalKeys(cfg)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Ignore the {keyID} argument as we only have a single server key so we always
|
// Ignore the {keyID} argument as we only have a single server key so we always
|
||||||
|
|
@ -100,16 +100,34 @@ func Setup(
|
||||||
"federation_get_event", cfg.Matrix.ServerName, keys,
|
"federation_get_event", cfg.Matrix.ServerName, keys,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||||
vars := mux.Vars(httpReq)
|
vars := mux.Vars(httpReq)
|
||||||
return readers.GetEvent(
|
return GetEvent(
|
||||||
httpReq.Context(), request, cfg, query, time.Now(), keys, vars["eventID"],
|
httpReq.Context(), request, cfg, query, time.Now(), keys, vars["eventID"],
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)).Methods("GET")
|
)).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 {
|
||||||
|
return RoomAliasToID(
|
||||||
|
httpReq, federation, cfg, aliasAPI,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)).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(
|
v1fedmux.Handle("/version", common.MakeExternalAPI(
|
||||||
"federation_version",
|
"federation_version",
|
||||||
func(httpReq *http.Request) util.JSONResponse {
|
func(httpReq *http.Request) util.JSONResponse {
|
||||||
return readers.Version()
|
return Version()
|
||||||
},
|
},
|
||||||
)).Methods("GET")
|
)).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.
|
// TODO: Check that the event is allowed by its auth_events.
|
||||||
|
|
||||||
// pass the event to the roomserver
|
// pass the event to the roomserver
|
||||||
if err := t.producer.SendEvents(t.context, []gomatrixserverlib.Event{e}, api.DoNotSendToOtherServers); err != nil {
|
return t.producer.SendEvents(t.context, []gomatrixserverlib.Event{e}, api.DoNotSendToOtherServers)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkAllowedByState(e gomatrixserverlib.Event, stateEvents []gomatrixserverlib.Event) error {
|
func checkAllowedByState(e gomatrixserverlib.Event, stateEvents []gomatrixserverlib.Event) error {
|
||||||
|
|
@ -218,8 +214,5 @@ func (t *txnReq) processEventWithMissingState(e gomatrixserverlib.Event) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// pass the event along with the state to the roomserver
|
// pass the event along with the state to the roomserver
|
||||||
if err := t.producer.SendEventWithState(t.context, state, e); err != nil {
|
return t.producer.SendEventWithState(t.context, state, e)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,7 @@ func buildMembershipEvent(
|
||||||
// them responded with an error.
|
// them responded with an error.
|
||||||
func sendToRemoteServer(
|
func sendToRemoteServer(
|
||||||
ctx context.Context, inv invite,
|
ctx context.Context, inv invite,
|
||||||
federation *gomatrixserverlib.FederationClient, cfg config.Dendrite,
|
federation *gomatrixserverlib.FederationClient, _ config.Dendrite,
|
||||||
builder gomatrixserverlib.EventBuilder,
|
builder gomatrixserverlib.EventBuilder,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
remoteServers := make([]gomatrixserverlib.ServerName, 2)
|
remoteServers := make([]gomatrixserverlib.ServerName, 2)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package readers
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/federationsender/queue"
|
"github.com/matrix-org/dendrite/federationsender/queue"
|
||||||
|
|
@ -27,6 +26,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/federationsender/types"
|
"github.com/matrix-org/dendrite/federationsender/types"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
sarama "gopkg.in/Shopify/sarama.v1"
|
sarama "gopkg.in/Shopify/sarama.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -134,6 +134,14 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if oldJoinedHosts == nil {
|
||||||
|
// This means that there is nothing to update as this is a duplicate
|
||||||
|
// message.
|
||||||
|
// This can happen if dendrite crashed between reading the message and
|
||||||
|
// persisting the stream position.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if ore.SendAsServer == api.DoNotSendToOtherServers {
|
if ore.SendAsServer == api.DoNotSendToOtherServers {
|
||||||
// Ignore event that we don't need to send anywhere.
|
// Ignore event that we don't need to send anywhere.
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -146,13 +154,9 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the event.
|
// Send the event.
|
||||||
if err = s.queues.SendEvent(
|
return s.queues.SendEvent(
|
||||||
&ore.Event, gomatrixserverlib.ServerName(ore.SendAsServer), joinedHostsAtEvent,
|
&ore.Event, gomatrixserverlib.ServerName(ore.SendAsServer), joinedHostsAtEvent,
|
||||||
); err != nil {
|
)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// joinedHostsAtEvent works out a list of matrix servers that were joined to
|
// joinedHostsAtEvent works out a list of matrix servers that were joined to
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// destinationQueue is a queue of events for a single destination.
|
// destinationQueue is a queue of events for a single destination.
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OutgoingQueues is a collection of queues for sending transactions to other
|
// OutgoingQueues is a collection of queues for sending transactions to other
|
||||||
|
|
|
||||||
|
|
@ -54,15 +54,14 @@ func (d *Database) prepare() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = d.PartitionOffsetStatements.Prepare(d.db, "federationsender"); err != nil {
|
return d.PartitionOffsetStatements.Prepare(d.db, "federationsender")
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRoom updates the joined hosts for a room and returns what the joined
|
// UpdateRoom updates the joined hosts for a room and returns what the joined
|
||||||
// hosts were before the update.
|
// hosts were before the update, or nil if this was a duplicate message.
|
||||||
|
// This is called when we receive a message from kafka, so we pass in
|
||||||
|
// oldEventID and newEventID to check that we haven't missed any messages or
|
||||||
|
// this isn't a duplicate message.
|
||||||
func (d *Database) UpdateRoom(
|
func (d *Database) UpdateRoom(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
roomID, oldEventID, newEventID string,
|
roomID, oldEventID, newEventID string,
|
||||||
|
|
@ -70,22 +69,34 @@ func (d *Database) UpdateRoom(
|
||||||
removeHosts []string,
|
removeHosts []string,
|
||||||
) (joinedHosts []types.JoinedHost, err error) {
|
) (joinedHosts []types.JoinedHost, err error) {
|
||||||
err = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
err = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
if err = d.insertRoom(ctx, txn, roomID); err != nil {
|
err = d.insertRoom(ctx, txn, roomID)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lastSentEventID, err := d.selectRoomForUpdate(ctx, txn, roomID)
|
lastSentEventID, err := d.selectRoomForUpdate(ctx, txn, roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if lastSentEventID == newEventID {
|
||||||
|
// We've handled this message before, so let's just ignore it.
|
||||||
|
// We can only get a duplicate for the last message we processed,
|
||||||
|
// so its enough just to compare the newEventID with lastSentEventID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if lastSentEventID != oldEventID {
|
if lastSentEventID != oldEventID {
|
||||||
return types.EventIDMismatchError{
|
return types.EventIDMismatchError{
|
||||||
DatabaseID: lastSentEventID, RoomServerID: oldEventID,
|
DatabaseID: lastSentEventID, RoomServerID: oldEventID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
joinedHosts, err = d.selectJoinedHosts(ctx, txn, roomID)
|
joinedHosts, err = d.selectJoinedHosts(ctx, txn, roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, add := range addHosts {
|
for _, add := range addHosts {
|
||||||
err = d.insertJoinedHosts(ctx, txn, roomID, add.MemberEventID, add.ServerName)
|
err = d.insertJoinedHosts(ctx, txn, roomID, add.MemberEventID, add.ServerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/mediaapi/types"
|
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPathFromBase64Hash evaluates the path to a media file from its Base64Hash
|
// GetPathFromBase64Hash evaluates the path to a media file from its Base64Hash
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/mediaapi/fileutils"
|
"github.com/matrix-org/dendrite/mediaapi/fileutils"
|
||||||
|
|
@ -38,6 +37,7 @@ import (
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const mediaIDCharacters = "A-Za-z0-9_=-"
|
const mediaIDCharacters = "A-Za-z0-9_=-"
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/mediaapi/fileutils"
|
"github.com/matrix-org/dendrite/mediaapi/fileutils"
|
||||||
|
|
@ -31,6 +30,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/mediaapi/types"
|
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// uploadRequest metadata included in or derivable from an upload request
|
// 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,
|
ctx, tmpDir, cfg.Media.AbsBasePath, db, cfg.Media.ThumbnailSizes,
|
||||||
activeThumbnailGeneration, cfg.Media.MaxThumbnailGenerators,
|
activeThumbnailGeneration, cfg.Media.MaxThumbnailGenerators,
|
||||||
); resErr != nil {
|
)
|
||||||
return resErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the uploadRequest fields
|
// Validate validates the uploadRequest fields
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,13 @@ type statements struct {
|
||||||
thumbnail thumbnailStatements
|
thumbnail thumbnailStatements
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statements) prepare(db *sql.DB) error {
|
func (s *statements) prepare(db *sql.DB) (err error) {
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = s.media.prepare(db); err != nil {
|
if err = s.media.prepare(db); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
if err = s.thumbnail.prepare(db); err != nil {
|
if err = s.thumbnail.prepare(db); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/mediaapi/storage"
|
"github.com/matrix-org/dendrite/mediaapi/storage"
|
||||||
"github.com/matrix-org/dendrite/mediaapi/types"
|
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type thumbnailFitness struct {
|
type thumbnailFitness struct {
|
||||||
|
|
@ -89,7 +89,7 @@ func SelectThumbnail(desired types.ThumbnailSize, thumbnails []*types.ThumbnailM
|
||||||
}
|
}
|
||||||
|
|
||||||
// getActiveThumbnailGeneration checks for active thumbnail generation
|
// 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.
|
// Check if there is active thumbnail generation.
|
||||||
activeThumbnailGeneration.Lock()
|
activeThumbnailGeneration.Lock()
|
||||||
defer activeThumbnailGeneration.Unlock()
|
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
|
// 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
|
// 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()
|
activeThumbnailGeneration.Lock()
|
||||||
defer activeThumbnailGeneration.Unlock()
|
defer activeThumbnailGeneration.Unlock()
|
||||||
if activeThumbnailGenerationResult, ok := activeThumbnailGeneration.PathToResult[string(dst)]; ok {
|
if activeThumbnailGenerationResult, ok := activeThumbnailGeneration.PathToResult[string(dst)]; ok {
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/mediaapi/storage"
|
"github.com/matrix-org/dendrite/mediaapi/storage"
|
||||||
"github.com/matrix-org/dendrite/mediaapi/types"
|
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/h2non/bimg.v1"
|
"gopkg.in/h2non/bimg.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,11 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/mediaapi/storage"
|
"github.com/matrix-org/dendrite/mediaapi/storage"
|
||||||
"github.com/matrix-org/dendrite/mediaapi/types"
|
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||||
"github.com/nfnt/resize"
|
"github.com/nfnt/resize"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateThumbnails generates the configured thumbnail sizes for the source file
|
// GenerateThumbnails generates the configured thumbnail sizes for the source file
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/publicroomsapi/storage"
|
"github.com/matrix-org/dendrite/publicroomsapi/storage"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
sarama "gopkg.in/Shopify/sarama.v1"
|
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
|
// At this point we've already committed the alias to the database so we
|
||||||
// shouldn't cancel this request.
|
// shouldn't cancel this request.
|
||||||
// TODO: Ensure that we send unsent events when if server restarts.
|
// TODO: Ensure that we send unsent events when if server restarts.
|
||||||
if err := r.sendUpdatedAliasesEvent(context.TODO(), request.UserID, request.RoomID); err != nil {
|
return r.sendUpdatedAliasesEvent(context.TODO(), request.UserID, request.RoomID)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAliasRoomID implements api.RoomserverAliasAPI
|
// 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
|
// At this point we've already committed the alias to the database so we
|
||||||
// shouldn't cancel this request.
|
// shouldn't cancel this request.
|
||||||
// TODO: Ensure that we send unsent events when if server restarts.
|
// TODO: Ensure that we send unsent events when if server restarts.
|
||||||
if err := r.sendUpdatedAliasesEvent(context.TODO(), request.UserID, roomID); err != nil {
|
return r.sendUpdatedAliasesEvent(context.TODO(), request.UserID, roomID)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type roomAliasesContent struct {
|
type roomAliasesContent struct {
|
||||||
|
|
|
||||||
|
|
@ -129,11 +129,7 @@ func processRoomEvent(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the extremities of the event graph for the room
|
// Update the extremities of the event graph for the room
|
||||||
if err := updateLatestEvents(ctx, db, ow, roomNID, stateAtEvent, event, input.SendAsServer); err != nil {
|
return updateLatestEvents(ctx, db, ow, roomNID, stateAtEvent, event, input.SendAsServer)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processInviteEvent(
|
func processInviteEvent(
|
||||||
|
|
|
||||||
|
|
@ -162,11 +162,7 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = u.updater.MarkEventAsSent(u.stateAtEvent.EventNID); err != nil {
|
return u.updater.MarkEventAsSent(u.stateAtEvent.EventNID)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *latestEventsUpdater) latestState() error {
|
func (u *latestEventsUpdater) latestState() error {
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||||
"github.com/matrix-org/dendrite/syncapi/sync"
|
"github.com/matrix-org/dendrite/syncapi/sync"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
sarama "gopkg.in/Shopify/sarama.v1"
|
sarama "gopkg.in/Shopify/sarama.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"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/sync"
|
||||||
"github.com/matrix-org/dendrite/syncapi/types"
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
sarama "gopkg.in/Shopify/sarama.v1"
|
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/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||||
"github.com/matrix-org/dendrite/syncapi/sync"
|
"github.com/matrix-org/dendrite/syncapi/sync"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
@ -28,7 +29,7 @@ import (
|
||||||
const pathPrefixR0 = "/_matrix/client/r0"
|
const pathPrefixR0 = "/_matrix/client/r0"
|
||||||
|
|
||||||
// Setup configures the given mux with sync-server listeners
|
// 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 := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
||||||
|
|
||||||
r0mux.Handle("/sync", common.MakeAuthAPI("sync", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
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 {
|
r0mux.Handle("/rooms/{roomID}/state", common.MakeAuthAPI("room_state", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
vars := mux.Vars(req)
|
vars := mux.Vars(req)
|
||||||
return srp.OnIncomingStateRequest(req, vars["roomID"])
|
return OnIncomingStateRequest(req, syncDB, vars["roomID"])
|
||||||
})).Methods("GET")
|
})).Methods("GET")
|
||||||
|
|
||||||
r0mux.Handle("/rooms/{roomID}/state/{type}", common.MakeAuthAPI("room_state", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
r0mux.Handle("/rooms/{roomID}/state/{type}", common.MakeAuthAPI("room_state", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
vars := mux.Vars(req)
|
vars := mux.Vars(req)
|
||||||
return srp.OnIncomingStateTypeRequest(req, vars["roomID"], vars["type"], "")
|
return OnIncomingStateTypeRequest(req, syncDB, vars["roomID"], vars["type"], "")
|
||||||
})).Methods("GET")
|
})).Methods("GET")
|
||||||
|
|
||||||
r0mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", common.MakeAuthAPI("room_state", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
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)
|
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")
|
})).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"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/syncapi/types"
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const outputRoomEventsSchema = `
|
const outputRoomEventsSchema = `
|
||||||
|
|
|
||||||
|
|
@ -229,44 +229,10 @@ func (d *SyncServerDatabase) IncrementalSync(
|
||||||
|
|
||||||
res := types.NewResponse(toPos)
|
res := types.NewResponse(toPos)
|
||||||
for _, delta := range deltas {
|
for _, delta := range deltas {
|
||||||
endPos := toPos
|
err = d.addRoomDeltaToResponse(ctx, txn, fromPos, toPos, delta, numRecentEventsPerRoom, res)
|
||||||
if delta.membershipPos > 0 && delta.membership == "leave" {
|
|
||||||
// make sure we don't leak recent events after the leave event.
|
|
||||||
// TODO: History visibility makes this somewhat complex to handle correctly. For example:
|
|
||||||
// TODO: This doesn't work for join -> leave in a single /sync request (see events prior to join).
|
|
||||||
// TODO: This will fail on join -> leave -> sensitive msg -> join -> leave
|
|
||||||
// in a single /sync request
|
|
||||||
// This is all "okay" assuming history_visibility == "shared" which it is by default.
|
|
||||||
endPos = delta.membershipPos
|
|
||||||
}
|
|
||||||
var recentStreamEvents []streamEvent
|
|
||||||
recentStreamEvents, err = d.events.selectRecentEvents(
|
|
||||||
ctx, txn, delta.roomID, fromPos, endPos, numRecentEventsPerRoom,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
recentEvents := streamEventsToEvents(recentStreamEvents)
|
|
||||||
delta.stateEvents = removeDuplicates(delta.stateEvents, recentEvents) // roll back
|
|
||||||
|
|
||||||
switch delta.membership {
|
|
||||||
case "join":
|
|
||||||
jr := types.NewJoinResponse()
|
|
||||||
jr.Timeline.Events = gomatrixserverlib.ToClientEvents(recentEvents, gomatrixserverlib.FormatSync)
|
|
||||||
jr.Timeline.Limited = false // TODO: if len(events) >= numRecents + 1 and then set limited:true
|
|
||||||
jr.State.Events = gomatrixserverlib.ToClientEvents(delta.stateEvents, gomatrixserverlib.FormatSync)
|
|
||||||
res.Rooms.Join[delta.roomID] = *jr
|
|
||||||
case "leave":
|
|
||||||
fallthrough // transitions to leave are the same as ban
|
|
||||||
case "ban":
|
|
||||||
// TODO: recentEvents may contain events that this user is not allowed to see because they are
|
|
||||||
// no longer in the room.
|
|
||||||
lr := types.NewLeaveResponse()
|
|
||||||
lr.Timeline.Events = gomatrixserverlib.ToClientEvents(recentEvents, gomatrixserverlib.FormatSync)
|
|
||||||
lr.Timeline.Limited = false // TODO: if len(events) >= numRecents + 1 and then set limited:true
|
|
||||||
lr.State.Events = gomatrixserverlib.ToClientEvents(delta.stateEvents, gomatrixserverlib.FormatSync)
|
|
||||||
res.Rooms.Leave[delta.roomID] = *lr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should be done in getStateDeltas
|
// TODO: This should be done in getStateDeltas
|
||||||
|
|
@ -418,6 +384,60 @@ func (d *SyncServerDatabase) addInvitesToResponse(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addRoomDeltaToResponse adds a room state delta to a sync response
|
||||||
|
func (d *SyncServerDatabase) addRoomDeltaToResponse(
|
||||||
|
ctx context.Context, txn *sql.Tx,
|
||||||
|
fromPos, toPos types.StreamPosition,
|
||||||
|
delta stateDelta,
|
||||||
|
numRecentEventsPerRoom int,
|
||||||
|
res *types.Response,
|
||||||
|
) error {
|
||||||
|
endPos := toPos
|
||||||
|
if delta.membershipPos > 0 && delta.membership == "leave" {
|
||||||
|
// make sure we don't leak recent events after the leave event.
|
||||||
|
// TODO: History visibility makes this somewhat complex to handle correctly. For example:
|
||||||
|
// TODO: This doesn't work for join -> leave in a single /sync request (see events prior to join).
|
||||||
|
// TODO: This will fail on join -> leave -> sensitive msg -> join -> leave
|
||||||
|
// in a single /sync request
|
||||||
|
// This is all "okay" assuming history_visibility == "shared" which it is by default.
|
||||||
|
endPos = delta.membershipPos
|
||||||
|
}
|
||||||
|
recentStreamEvents, err := d.events.selectRecentEvents(
|
||||||
|
ctx, txn, delta.roomID, fromPos, endPos, numRecentEventsPerRoom,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
recentEvents := streamEventsToEvents(recentStreamEvents)
|
||||||
|
delta.stateEvents = removeDuplicates(delta.stateEvents, recentEvents) // roll back
|
||||||
|
|
||||||
|
// Don't bother appending empty room entries
|
||||||
|
if len(recentEvents) == 0 && len(delta.stateEvents) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch delta.membership {
|
||||||
|
case "join":
|
||||||
|
jr := types.NewJoinResponse()
|
||||||
|
jr.Timeline.Events = gomatrixserverlib.ToClientEvents(recentEvents, gomatrixserverlib.FormatSync)
|
||||||
|
jr.Timeline.Limited = false // TODO: if len(events) >= numRecents + 1 and then set limited:true
|
||||||
|
jr.State.Events = gomatrixserverlib.ToClientEvents(delta.stateEvents, gomatrixserverlib.FormatSync)
|
||||||
|
res.Rooms.Join[delta.roomID] = *jr
|
||||||
|
case "leave":
|
||||||
|
fallthrough // transitions to leave are the same as ban
|
||||||
|
case "ban":
|
||||||
|
// TODO: recentEvents may contain events that this user is not allowed to see because they are
|
||||||
|
// no longer in the room.
|
||||||
|
lr := types.NewLeaveResponse()
|
||||||
|
lr.Timeline.Events = gomatrixserverlib.ToClientEvents(recentEvents, gomatrixserverlib.FormatSync)
|
||||||
|
lr.Timeline.Limited = false // TODO: if len(events) >= numRecents + 1 and then set limited:true
|
||||||
|
lr.State.Events = gomatrixserverlib.ToClientEvents(delta.stateEvents, gomatrixserverlib.FormatSync)
|
||||||
|
res.Rooms.Leave[delta.roomID] = *lr
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// fetchStateEvents converts the set of event IDs into a set of events. It will fetch any which are missing from the database.
|
// fetchStateEvents converts the set of event IDs into a set of events. It will fetch any which are missing from the database.
|
||||||
// Returns a map of room ID to list of events.
|
// Returns a map of room ID to list of events.
|
||||||
func (d *SyncServerDatabase) fetchStateEvents(
|
func (d *SyncServerDatabase) fetchStateEvents(
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,12 @@ package sync
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||||
"github.com/matrix-org/dendrite/syncapi/types"
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Notifier will wake up sleeping requests when there is some new data.
|
// Notifier will wake up sleeping requests when there is some new data.
|
||||||
|
|
@ -38,6 +39,8 @@ type Notifier struct {
|
||||||
currPos types.StreamPosition
|
currPos types.StreamPosition
|
||||||
// A map of user_id => UserStream which can be used to wake a given user's /sync request.
|
// A map of user_id => UserStream which can be used to wake a given user's /sync request.
|
||||||
userStreams map[string]*UserStream
|
userStreams map[string]*UserStream
|
||||||
|
// The last time we cleaned out stale entries from the userStreams map
|
||||||
|
lastCleanUpTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNotifier creates a new notifier set to the given stream position.
|
// NewNotifier creates a new notifier set to the given stream position.
|
||||||
|
|
@ -49,6 +52,7 @@ func NewNotifier(pos types.StreamPosition) *Notifier {
|
||||||
roomIDToJoinedUsers: make(map[string]userIDSet),
|
roomIDToJoinedUsers: make(map[string]userIDSet),
|
||||||
userStreams: make(map[string]*UserStream),
|
userStreams: make(map[string]*UserStream),
|
||||||
streamLock: &sync.Mutex{},
|
streamLock: &sync.Mutex{},
|
||||||
|
lastCleanUpTime: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,6 +67,8 @@ func (n *Notifier) OnNewEvent(ev *gomatrixserverlib.Event, userID string, pos ty
|
||||||
defer n.streamLock.Unlock()
|
defer n.streamLock.Unlock()
|
||||||
n.currPos = pos
|
n.currPos = pos
|
||||||
|
|
||||||
|
n.removeEmptyUserStreams()
|
||||||
|
|
||||||
if ev != nil {
|
if ev != nil {
|
||||||
// Map this event's room_id to a list of joined users, and wake them up.
|
// Map this event's room_id to a list of joined users, and wake them up.
|
||||||
userIDs := n.joinedUsers(ev.RoomID())
|
userIDs := n.joinedUsers(ev.RoomID())
|
||||||
|
|
@ -100,8 +106,10 @@ func (n *Notifier) OnNewEvent(ev *gomatrixserverlib.Event, userID string, pos ty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForEvents blocks until there are new events for this request.
|
// GetListener returns a UserStreamListener that can be used to wait for
|
||||||
func (n *Notifier) WaitForEvents(req syncRequest) types.StreamPosition {
|
// updates for a user. Must be closed.
|
||||||
|
// notify for anything before sincePos
|
||||||
|
func (n *Notifier) GetListener(req syncRequest) UserStreamListener {
|
||||||
// Do what synapse does: https://github.com/matrix-org/synapse/blob/v0.20.0/synapse/notifier.py#L298
|
// Do what synapse does: https://github.com/matrix-org/synapse/blob/v0.20.0/synapse/notifier.py#L298
|
||||||
// - Bucket request into a lookup map keyed off a list of joined room IDs and separately a user ID
|
// - Bucket request into a lookup map keyed off a list of joined room IDs and separately a user ID
|
||||||
// - Incoming events wake requests for a matching room ID
|
// - Incoming events wake requests for a matching room ID
|
||||||
|
|
@ -110,25 +118,12 @@ func (n *Notifier) WaitForEvents(req syncRequest) types.StreamPosition {
|
||||||
// TODO: v1 /events 'peeking' has an 'explicit room ID' which is also tracked,
|
// TODO: v1 /events 'peeking' has an 'explicit room ID' which is also tracked,
|
||||||
// but given we don't do /events, let's pretend it doesn't exist.
|
// but given we don't do /events, let's pretend it doesn't exist.
|
||||||
|
|
||||||
// In a guard, check if the /sync request should block, and block it until we get woken up
|
|
||||||
n.streamLock.Lock()
|
n.streamLock.Lock()
|
||||||
currentPos := n.currPos
|
defer n.streamLock.Unlock()
|
||||||
|
|
||||||
// TODO: We increment the stream position for any event, so it's possible that we return immediately
|
n.removeEmptyUserStreams()
|
||||||
// with a pos which contains no new events for this user. We should probably re-wait for events
|
|
||||||
// automatically in this case.
|
|
||||||
if req.since != currentPos {
|
|
||||||
n.streamLock.Unlock()
|
|
||||||
return currentPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait to be woken up, and then re-check the stream position
|
return n.fetchUserStream(req.userID, true).GetListener(req.ctx)
|
||||||
req.log.WithField("user_id", req.userID).Info("Waiting for event")
|
|
||||||
|
|
||||||
// give up the stream lock prior to waiting on the user lock
|
|
||||||
stream := n.fetchUserStream(req.userID, true)
|
|
||||||
n.streamLock.Unlock()
|
|
||||||
return stream.Wait(currentPos)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the membership states required to notify users correctly.
|
// Load the membership states required to notify users correctly.
|
||||||
|
|
@ -141,6 +136,11 @@ func (n *Notifier) Load(ctx context.Context, db *storage.SyncServerDatabase) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CurrentPosition returns the current stream position
|
||||||
|
func (n *Notifier) CurrentPosition() types.StreamPosition {
|
||||||
|
return n.currPos
|
||||||
|
}
|
||||||
|
|
||||||
// setUsersJoinedToRooms marks the given users as 'joined' to the given rooms, such that new events from
|
// setUsersJoinedToRooms marks the given users as 'joined' to the given rooms, such that new events from
|
||||||
// these rooms will wake the given users /sync requests. This should be called prior to ANY calls to
|
// these rooms will wake the given users /sync requests. This should be called prior to ANY calls to
|
||||||
// OnNewEvent (eg on startup) to prevent racing.
|
// OnNewEvent (eg on startup) to prevent racing.
|
||||||
|
|
@ -171,7 +171,7 @@ func (n *Notifier) fetchUserStream(userID string, makeIfNotExists bool) *UserStr
|
||||||
stream, ok := n.userStreams[userID]
|
stream, ok := n.userStreams[userID]
|
||||||
if !ok && makeIfNotExists {
|
if !ok && makeIfNotExists {
|
||||||
// TODO: Unbounded growth of streams (1 per user)
|
// TODO: Unbounded growth of streams (1 per user)
|
||||||
stream = NewUserStream(userID)
|
stream = NewUserStream(userID, n.currPos)
|
||||||
n.userStreams[userID] = stream
|
n.userStreams[userID] = stream
|
||||||
}
|
}
|
||||||
return stream
|
return stream
|
||||||
|
|
@ -201,6 +201,29 @@ func (n *Notifier) joinedUsers(roomID string) (userIDs []string) {
|
||||||
return n.roomIDToJoinedUsers[roomID].values()
|
return n.roomIDToJoinedUsers[roomID].values()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removeEmptyUserStreams iterates through the user stream map and removes any
|
||||||
|
// that have been empty for a certain amount of time. This is a crude way of
|
||||||
|
// ensuring that the userStreams map doesn't grow forver.
|
||||||
|
// This should be called when the notifier gets called for whatever reason,
|
||||||
|
// the function itself is responsible for ensuring it doesn't iterate too
|
||||||
|
// often.
|
||||||
|
// NB: Callers should have locked the mutex before calling this function.
|
||||||
|
func (n *Notifier) removeEmptyUserStreams() {
|
||||||
|
// Only clean up now and again
|
||||||
|
now := time.Now()
|
||||||
|
if n.lastCleanUpTime.Add(time.Minute).After(now) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n.lastCleanUpTime = now
|
||||||
|
|
||||||
|
deleteBefore := now.Add(-5 * time.Minute)
|
||||||
|
for key, value := range n.userStreams {
|
||||||
|
if value.TimeOfLastNonEmpty().Before(deleteBefore) {
|
||||||
|
delete(n.userStreams, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A string set, mainly existing for improving clarity of structs in this file.
|
// A string set, mainly existing for improving clarity of structs in this file.
|
||||||
type userIDSet map[string]bool
|
type userIDSet map[string]bool
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -256,24 +256,22 @@ func TestNewEventAndWasPreviouslyJoinedToRoom(t *testing.T) {
|
||||||
|
|
||||||
// same as Notifier.WaitForEvents but with a timeout.
|
// same as Notifier.WaitForEvents but with a timeout.
|
||||||
func waitForEvents(n *Notifier, req syncRequest) (types.StreamPosition, error) {
|
func waitForEvents(n *Notifier, req syncRequest) (types.StreamPosition, error) {
|
||||||
done := make(chan types.StreamPosition, 1)
|
listener := n.GetListener(req)
|
||||||
go func() {
|
defer listener.Close()
|
||||||
newPos := n.WaitForEvents(req)
|
|
||||||
done <- newPos
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(5 * time.Second):
|
case <-time.After(5 * time.Second):
|
||||||
return types.StreamPosition(0), fmt.Errorf(
|
return types.StreamPosition(0), fmt.Errorf(
|
||||||
"waitForEvents timed out waiting for %s (pos=%d)", req.userID, req.since,
|
"waitForEvents timed out waiting for %s (pos=%d)", req.userID, req.since,
|
||||||
)
|
)
|
||||||
case p := <-done:
|
case <-listener.GetNotifyChannel(req.since):
|
||||||
|
p := listener.GetStreamPosition()
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until something is Wait()ing on the user stream.
|
// Wait until something is Wait()ing on the user stream.
|
||||||
func waitForBlocking(s *UserStream, numBlocking int) {
|
func waitForBlocking(s *UserStream, numBlocking uint) {
|
||||||
for numBlocking != s.NumWaiting() {
|
for numBlocking != s.NumWaiting() {
|
||||||
// This is horrible but I don't want to add a signalling mechanism JUST for testing.
|
// This is horrible but I don't want to add a signalling mechanism JUST for testing.
|
||||||
time.Sleep(1 * time.Microsecond)
|
time.Sleep(1 * time.Microsecond)
|
||||||
|
|
@ -288,5 +286,6 @@ func newTestSyncRequest(userID string, since types.StreamPosition) syncRequest {
|
||||||
wantFullState: false,
|
wantFullState: false,
|
||||||
limit: defaultTimelineLimit,
|
limit: defaultTimelineLimit,
|
||||||
log: util.GetLogger(context.TODO()),
|
log: util.GetLogger(context.TODO()),
|
||||||
|
ctx: context.TODO(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/syncapi/types"
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultSyncTimeout = time.Duration(30) * time.Second
|
const defaultSyncTimeout = time.Duration(30) * time.Second
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,9 @@
|
||||||
package sync
|
package sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
|
@ -28,6 +26,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/syncapi/types"
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequestPool manages HTTP long-poll connections for /sync
|
// RequestPool manages HTTP long-poll connections for /sync
|
||||||
|
|
@ -62,146 +61,78 @@ func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *authtype
|
||||||
"timeout": syncReq.timeout,
|
"timeout": syncReq.timeout,
|
||||||
}).Info("Incoming /sync request")
|
}).Info("Incoming /sync request")
|
||||||
|
|
||||||
// Fork off 2 goroutines: one to do the work, and one to serve as a timeout.
|
currPos := rp.notifier.CurrentPosition()
|
||||||
// Whichever returns first is the one we will serve back to the client.
|
|
||||||
timeoutChan := make(chan struct{})
|
|
||||||
timer := time.AfterFunc(syncReq.timeout, func() {
|
|
||||||
close(timeoutChan) // signal that the timeout has expired
|
|
||||||
})
|
|
||||||
|
|
||||||
done := make(chan util.JSONResponse)
|
// If this is an initial sync or timeout=0 we return immediately
|
||||||
go func() {
|
if syncReq.since == types.StreamPosition(0) || syncReq.timeout == 0 {
|
||||||
currentPos := rp.notifier.WaitForEvents(*syncReq)
|
syncData, err := rp.currentSyncForUser(*syncReq, currPos)
|
||||||
// We stop the timer BEFORE calculating the response so the cpu work
|
|
||||||
// done to calculate the response is not timed. This stops us from
|
|
||||||
// doing lots of work then timing out and sending back an empty response.
|
|
||||||
timer.Stop()
|
|
||||||
syncData, err := rp.currentSyncForUser(*syncReq, currentPos)
|
|
||||||
var res util.JSONResponse
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res = httputil.LogThenError(req, err)
|
return httputil.LogThenError(req, err)
|
||||||
} else {
|
|
||||||
syncData, err = rp.appendAccountData(syncData, device.UserID, *syncReq, currentPos)
|
|
||||||
if err != nil {
|
|
||||||
res = httputil.LogThenError(req, err)
|
|
||||||
} else {
|
|
||||||
res = util.JSONResponse{
|
|
||||||
Code: 200,
|
|
||||||
JSON: syncData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
done <- res
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-timeoutChan: // timeout fired
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 200,
|
Code: 200,
|
||||||
JSON: types.NewResponse(syncReq.since),
|
JSON: syncData,
|
||||||
}
|
}
|
||||||
case res := <-done: // received a response
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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{}
|
// Otherwise, we wait for the notifier to tell us if something *may* have
|
||||||
// Fill the prev_content and replaces_state keys if necessary
|
// happened. We loop in case it turns out that nothing did happen.
|
||||||
for _, event := range stateEvents {
|
|
||||||
stateEvent := stateEventInStateResp{
|
timer := time.NewTimer(syncReq.timeout) // case of timeout=0 is handled above
|
||||||
ClientEvent: gomatrixserverlib.ToClientEvent(event, gomatrixserverlib.FormatAll),
|
defer timer.Stop()
|
||||||
|
|
||||||
|
userStreamListener := rp.notifier.GetListener(*syncReq)
|
||||||
|
defer userStreamListener.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
// Wait for notifier to wake us up
|
||||||
|
case <-userStreamListener.GetNotifyChannel(currPos):
|
||||||
|
currPos = userStreamListener.GetStreamPosition()
|
||||||
|
// Or for timeout to expire
|
||||||
|
case <-timer.C:
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: types.NewResponse(syncReq.since),
|
||||||
|
}
|
||||||
|
// Or for the request to be cancelled
|
||||||
|
case <-req.Context().Done():
|
||||||
|
return httputil.LogThenError(req, req.Context().Err())
|
||||||
}
|
}
|
||||||
var prevEventRef types.PrevEventRef
|
|
||||||
if len(event.Unsigned()) > 0 {
|
// Note that we don't time out during calculation of sync
|
||||||
if err := json.Unmarshal(event.Unsigned(), &prevEventRef); err != nil {
|
// response. This ensures that we don't waste the hard work
|
||||||
return httputil.LogThenError(req, err)
|
// of calculating the sync only to get timed out before we
|
||||||
}
|
// can respond
|
||||||
// Fills the previous state event ID if the state event replaces another
|
|
||||||
// state event
|
syncData, err := rp.currentSyncForUser(*syncReq, currPos)
|
||||||
if len(prevEventRef.ReplacesState) > 0 {
|
if err != nil {
|
||||||
stateEvent.ReplacesState = prevEventRef.ReplacesState
|
return httputil.LogThenError(req, err)
|
||||||
}
|
}
|
||||||
// Fill the previous event if the state event references a previous event
|
if !syncData.IsEmpty() {
|
||||||
if prevEventRef.PrevContent != nil {
|
return util.JSONResponse{
|
||||||
stateEvent.PrevContent = prevEventRef.PrevContent
|
Code: 200,
|
||||||
|
JSON: syncData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = append(resp, stateEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: 200,
|
|
||||||
JSON: resp,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnIncomingStateTypeRequest is called when a client makes a
|
func (rp *RequestPool) currentSyncForUser(req syncRequest, currentPos types.StreamPosition) (res *types.Response, err error) {
|
||||||
// /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) (*types.Response, error) {
|
|
||||||
// TODO: handle ignored users
|
// TODO: handle ignored users
|
||||||
if req.since == types.StreamPosition(0) {
|
if req.since == types.StreamPosition(0) {
|
||||||
return rp.db.CompleteSync(req.ctx, req.userID, req.limit)
|
res, err = rp.db.CompleteSync(req.ctx, req.userID, req.limit)
|
||||||
|
} else {
|
||||||
|
res, err = rp.db.IncrementalSync(req.ctx, req.userID, req.since, currentPos, req.limit)
|
||||||
}
|
}
|
||||||
return rp.db.IncrementalSync(req.ctx, req.userID, req.since, currentPos, req.limit)
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err = rp.appendAccountData(res, req.userID, req, currentPos)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *RequestPool) appendAccountData(
|
func (rp *RequestPool) appendAccountData(
|
||||||
|
|
|
||||||
|
|
@ -15,65 +15,148 @@
|
||||||
package sync
|
package sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/syncapi/types"
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserStream represents a communication mechanism between the /sync request goroutine
|
// UserStream represents a communication mechanism between the /sync request goroutine
|
||||||
// and the underlying sync server goroutines. Goroutines can Wait() for a stream position and
|
// and the underlying sync server goroutines.
|
||||||
// goroutines can Broadcast(streamPosition) to other goroutines.
|
// Goroutines can get a UserStreamListener to wait for updates, and can Broadcast()
|
||||||
|
// updates.
|
||||||
type UserStream struct {
|
type UserStream struct {
|
||||||
UserID string
|
UserID string
|
||||||
// Because this is a Cond, we can notify all waiting goroutines so this works
|
// The lock that protects changes to this struct
|
||||||
// across devices for the same user. Protects pos.
|
lock sync.Mutex
|
||||||
cond *sync.Cond
|
// Closed when there is an update.
|
||||||
// The position to broadcast to callers of Wait().
|
signalChannel chan struct{}
|
||||||
|
// The last stream position that there may have been an update for the suser
|
||||||
pos types.StreamPosition
|
pos types.StreamPosition
|
||||||
// The number of goroutines blocked on Wait() - used for testing and metrics
|
// The last time when we had some listeners waiting
|
||||||
numWaiting int
|
timeOfLastChannel time.Time
|
||||||
|
// The number of listeners waiting
|
||||||
|
numWaiting uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserStreamListener allows a sync request to wait for updates for a user.
|
||||||
|
type UserStreamListener struct {
|
||||||
|
userStream *UserStream
|
||||||
|
|
||||||
|
// Whether the stream has been closed
|
||||||
|
hasClosed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserStream creates a new user stream
|
// NewUserStream creates a new user stream
|
||||||
func NewUserStream(userID string) *UserStream {
|
func NewUserStream(userID string, currPos types.StreamPosition) *UserStream {
|
||||||
return &UserStream{
|
return &UserStream{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
cond: sync.NewCond(&sync.Mutex{}),
|
timeOfLastChannel: time.Now(),
|
||||||
|
pos: currPos,
|
||||||
|
signalChannel: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait blocks until there is a new stream position for this user, which is then returned.
|
// GetListener returns UserStreamListener that a sync request can use to wait
|
||||||
// waitAtPos should be the position the stream thinks it should be waiting at.
|
// for new updates with.
|
||||||
func (s *UserStream) Wait(waitAtPos types.StreamPosition) (pos types.StreamPosition) {
|
// UserStreamListener must be closed
|
||||||
s.cond.L.Lock()
|
func (s *UserStream) GetListener(ctx context.Context) UserStreamListener {
|
||||||
// Before we start blocking, we need to make sure that we didn't race with a call
|
s.lock.Lock()
|
||||||
// to Broadcast() between calling Wait() and actually sleeping. We check the last
|
defer s.lock.Unlock()
|
||||||
// broadcast pos to see if it is newer than the pos we are meant to wait at. If it
|
|
||||||
// is newer, something has Broadcast to this stream more recently so return immediately.
|
s.numWaiting++ // We decrement when UserStreamListener is closed
|
||||||
if s.pos > waitAtPos {
|
|
||||||
pos = s.pos
|
listener := UserStreamListener{
|
||||||
s.cond.L.Unlock()
|
userStream: s,
|
||||||
return
|
|
||||||
}
|
}
|
||||||
s.numWaiting++
|
|
||||||
s.cond.Wait()
|
// Lets be a bit paranoid here and check that Close() is being called
|
||||||
pos = s.pos
|
runtime.SetFinalizer(&listener, func(l *UserStreamListener) {
|
||||||
s.numWaiting--
|
if !l.hasClosed {
|
||||||
s.cond.L.Unlock()
|
util.GetLogger(ctx).Warn("Didn't call Close on UserStreamListener")
|
||||||
return
|
l.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return listener
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast a new stream position for this user.
|
// Broadcast a new stream position for this user.
|
||||||
func (s *UserStream) Broadcast(pos types.StreamPosition) {
|
func (s *UserStream) Broadcast(pos types.StreamPosition) {
|
||||||
s.cond.L.Lock()
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
s.pos = pos
|
s.pos = pos
|
||||||
s.cond.L.Unlock()
|
|
||||||
s.cond.Broadcast()
|
close(s.signalChannel)
|
||||||
|
|
||||||
|
s.signalChannel = make(chan struct{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumWaiting returns the number of goroutines waiting for Wait() to return. Used for metrics and testing.
|
// NumWaiting returns the number of goroutines waiting for waiting for updates.
|
||||||
func (s *UserStream) NumWaiting() int {
|
// Used for metrics and testing.
|
||||||
s.cond.L.Lock()
|
func (s *UserStream) NumWaiting() uint {
|
||||||
defer s.cond.L.Unlock()
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
return s.numWaiting
|
return s.numWaiting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TimeOfLastNonEmpty returns the last time that the number of waiting listeners
|
||||||
|
// was non-empty, may be time.Now() if number of waiting listeners is currently
|
||||||
|
// non-empty.
|
||||||
|
func (s *UserStream) TimeOfLastNonEmpty() time.Time {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
if s.numWaiting > 0 {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.timeOfLastChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStreamPosition returns last stream position which the UserStream was
|
||||||
|
// notified about
|
||||||
|
func (s *UserStreamListener) GetStreamPosition() types.StreamPosition {
|
||||||
|
s.userStream.lock.Lock()
|
||||||
|
defer s.userStream.lock.Unlock()
|
||||||
|
|
||||||
|
return s.userStream.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNotifyChannel returns a channel that is closed when there may be an
|
||||||
|
// update for the user.
|
||||||
|
// sincePos specifies from which point we want to be notified about. If there
|
||||||
|
// has already been an update after sincePos we'll return a closed channel
|
||||||
|
// immediately.
|
||||||
|
func (s *UserStreamListener) GetNotifyChannel(sincePos types.StreamPosition) <-chan struct{} {
|
||||||
|
s.userStream.lock.Lock()
|
||||||
|
defer s.userStream.lock.Unlock()
|
||||||
|
|
||||||
|
if sincePos < s.userStream.pos {
|
||||||
|
// If the listener is behind, i.e. missed a potential update, then we
|
||||||
|
// want them to wake up immediately. We do this by returning a new
|
||||||
|
// closed stream, which returns immediately when selected.
|
||||||
|
closedChannel := make(chan struct{})
|
||||||
|
close(closedChannel)
|
||||||
|
return closedChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.userStream.signalChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close cleans up resources used
|
||||||
|
func (s *UserStreamListener) Close() {
|
||||||
|
s.userStream.lock.Lock()
|
||||||
|
defer s.userStream.lock.Unlock()
|
||||||
|
|
||||||
|
if !s.hasClosed {
|
||||||
|
s.userStream.numWaiting--
|
||||||
|
s.userStream.timeOfLastChannel = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.hasClosed = true
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,16 @@ func NewResponse(pos StreamPosition) *Response {
|
||||||
return &res
|
return &res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if the response is empty, i.e. used to decided whether
|
||||||
|
// to return the response immediately to the client or to wait for more data.
|
||||||
|
func (r *Response) IsEmpty() bool {
|
||||||
|
return len(r.Rooms.Join) == 0 &&
|
||||||
|
len(r.Rooms.Invite) == 0 &&
|
||||||
|
len(r.Rooms.Leave) == 0 &&
|
||||||
|
len(r.AccountData.Events) == 0 &&
|
||||||
|
len(r.Presence.Events) == 0
|
||||||
|
}
|
||||||
|
|
||||||
// JoinResponse represents a /sync response for a room which is under the 'join' key.
|
// JoinResponse represents a /sync response for a room which is under the 'join' key.
|
||||||
type JoinResponse struct {
|
type JoinResponse struct {
|
||||||
State struct {
|
State struct {
|
||||||
|
|
|
||||||
6
vendor/manifest
vendored
6
vendor/manifest
vendored
|
|
@ -10,7 +10,7 @@
|
||||||
{
|
{
|
||||||
"importpath": "github.com/alecthomas/gometalinter",
|
"importpath": "github.com/alecthomas/gometalinter",
|
||||||
"repository": "https://github.com/alecthomas/gometalinter",
|
"repository": "https://github.com/alecthomas/gometalinter",
|
||||||
"revision": "5507b26af3204e949ffe50ec08ee73e5847938e1",
|
"revision": "0262fb20957a4c2d3bb7c834a6a125ae3884a2c6",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -135,13 +135,13 @@
|
||||||
{
|
{
|
||||||
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
||||||
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
||||||
"revision": "fb17c27f65a0699b0d15f5311a530225b4aea5e0",
|
"revision": "076933f95312aae3a9476e78d6b4118e1b45d542",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"importpath": "github.com/matrix-org/naffka",
|
"importpath": "github.com/matrix-org/naffka",
|
||||||
"repository": "https://github.com/matrix-org/naffka",
|
"repository": "https://github.com/matrix-org/naffka",
|
||||||
"revision": "d28656e34f96a8eeaab53e3b7678c9ce14af5786",
|
"revision": "662bfd0841d0194bfe0a700d54226bb96eac574d",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
- [2. Analyse the debug output](#2-analyse-the-debug-output)
|
- [2. Analyse the debug output](#2-analyse-the-debug-output)
|
||||||
- [3. Report an issue.](#3-report-an-issue)
|
- [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)
|
- [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)
|
- [Checkstyle XML format](#checkstyle-xml-format)
|
||||||
|
|
||||||
<!-- /MarkdownTOC -->
|
<!-- /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 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.
|
- [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](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.
|
- [deadcode](https://github.com/tsenart/deadcode) - Finds unused code.
|
||||||
- [gocyclo](https://github.com/alecthomas/gocyclo) - Computes the cyclomatic complexity of functions.
|
- [gocyclo](https://github.com/alecthomas/gocyclo) - Computes the cyclomatic complexity of functions.
|
||||||
- [golint](https://github.com/golang/lint) - Google's (mostly stylistic) linter.
|
- [golint](https://github.com/golang/lint) - Google's (mostly stylistic) linter.
|
||||||
- [varcheck](https://github.com/opennota/check) - Find unused global variables and constants.
|
- [varcheck](https://github.com/opennota/check) - Find unused global variables and constants.
|
||||||
- [structcheck](https://github.com/opennota/check) - Find unused struct fields.
|
- [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.
|
- [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.
|
- [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.
|
- [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.
|
- [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`).
|
- [lll](https://github.com/walle/lll) - Report long lines (see `--line-length=N`).
|
||||||
- [misspell](https://github.com/client9/misspell) - Finds commonly misspelled English words.
|
- [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.
|
- [unparam](https://github.com/mvdan/unparam) - Find unused function parameters.
|
||||||
- [unused](https://github.com/dominikh/go-tools/tree/master/cmd/unused) - Find unused variables.
|
- [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.
|
- [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
|
## Configuration file
|
||||||
|
|
||||||
gometalinter now supports a JSON configuration file which can be loaded via
|
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
|
`--config=<file>`. The format of this file is determined by the `Config` struct
|
||||||
in `config.go`.
|
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:
|
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.
|
- 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
|
- "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:
|
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
|
## Installing
|
||||||
|
|
||||||
There are two options for installing gometalinter.
|
There are two options for installing gometalinter.
|
||||||
|
|
@ -171,7 +201,8 @@ Install all known linters:
|
||||||
$ gometalinter --install
|
$ gometalinter --install
|
||||||
Installing:
|
Installing:
|
||||||
structcheck
|
structcheck
|
||||||
aligncheck
|
maligned
|
||||||
|
nakedret
|
||||||
deadcode
|
deadcode
|
||||||
gocyclo
|
gocyclo
|
||||||
ineffassign
|
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.
|
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
|
## Checkstyle XML format
|
||||||
|
|
||||||
`gometalinter` supports [checkstyle](http://checkstyle.sourceforge.net/)
|
`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()
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue