mirror of
https://github.com/status-im/status-go.git
synced 2025-01-11 15:14:52 +00:00
feat_: LogOnPanic linter
This commit is contained in:
parent
3179532b64
commit
e889114df6
5
Makefile
5
Makefile
@ -407,7 +407,10 @@ canary-test: node-canary
|
||||
# TODO: uncomment that!
|
||||
#_assets/scripts/canary_test_mailservers.sh ./config/cli/fleet-eth.prod.json
|
||||
|
||||
lint: generate
|
||||
lint-panics: generate
|
||||
go run ./cmd/lint-panics --test=false ./...
|
||||
|
||||
lint: generate lint-panics
|
||||
golangci-lint run ./...
|
||||
|
||||
ci: generate lint canary-test test-unit test-e2e ##@tests Run all linters and tests at once
|
||||
|
101
cmd/lint-panics/gopls/client.go
Normal file
101
cmd/lint-panics/gopls/client.go
Normal file
@ -0,0 +1,101 @@
|
||||
package gopls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.lsp.dev/protocol"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type DummyClient struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewDummyClient(logger *zap.Logger) *DummyClient {
|
||||
return &DummyClient{
|
||||
logger: nil, //logger.Named("client"),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DummyClient) Progress(ctx context.Context, params *protocol.ProgressParams) (err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: Progress", zap.Any("params", params))
|
||||
}
|
||||
return
|
||||
}
|
||||
func (d *DummyClient) WorkDoneProgressCreate(ctx context.Context, params *protocol.WorkDoneProgressCreateParams) (err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: WorkDoneProgressCreate")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DummyClient) LogMessage(ctx context.Context, params *protocol.LogMessageParams) (err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: LogMessage", zap.Any("message", params))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DummyClient) PublishDiagnostics(ctx context.Context, params *protocol.PublishDiagnosticsParams) (err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: PublishDiagnostics")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DummyClient) ShowMessage(ctx context.Context, params *protocol.ShowMessageParams) (err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: ShowMessage", zap.Any("message", params))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DummyClient) ShowMessageRequest(ctx context.Context, params *protocol.ShowMessageRequestParams) (result *protocol.MessageActionItem, err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: ShowMessageRequest", zap.Any("message", params))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *DummyClient) Telemetry(ctx context.Context, params interface{}) (err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: Telemetry")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DummyClient) RegisterCapability(ctx context.Context, params *protocol.RegistrationParams) (err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: RegisterCapability")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DummyClient) UnregisterCapability(ctx context.Context, params *protocol.UnregistrationParams) (err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: UnregisterCapability")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DummyClient) ApplyEdit(ctx context.Context, params *protocol.ApplyWorkspaceEditParams) (result bool, err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: ApplyEdit")
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *DummyClient) Configuration(ctx context.Context, params *protocol.ConfigurationParams) (result []interface{}, err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: Configuration")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *DummyClient) WorkspaceFolders(ctx context.Context) (result []protocol.WorkspaceFolder, err error) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("client: WorkspaceFolders")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
150
cmd/lint-panics/gopls/gopls.go
Normal file
150
cmd/lint-panics/gopls/gopls.go
Normal file
@ -0,0 +1,150 @@
|
||||
package gopls
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"go.lsp.dev/jsonrpc2"
|
||||
"go.lsp.dev/protocol"
|
||||
"context"
|
||||
|
||||
"time"
|
||||
"go.uber.org/zap"
|
||||
"go.lsp.dev/uri"
|
||||
)
|
||||
|
||||
type Connection struct {
|
||||
logger *zap.Logger
|
||||
server protocol.Server
|
||||
cmd *exec.Cmd
|
||||
conn jsonrpc2.Conn
|
||||
}
|
||||
|
||||
func NewGoplsClient(ctx context.Context, logger *zap.Logger, rootDir string) *Connection {
|
||||
var err error
|
||||
|
||||
logger.Debug("initializing gopls client")
|
||||
|
||||
gopls := &Connection{
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
client := NewDummyClient(logger)
|
||||
|
||||
// Create a JSON-RPC connection using stdin and stdout
|
||||
gopls.cmd = exec.Command("gopls", "serve")
|
||||
|
||||
stdin, err := gopls.cmd.StdinPipe()
|
||||
if err != nil {
|
||||
logger.Error("Failed to get stdin pipe", zap.Error(err))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stdout, err := gopls.cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
logger.Error("Failed to get stdout pipe", zap.Error(err))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = gopls.cmd.Start()
|
||||
if err != nil {
|
||||
logger.Error("Failed to start gopls", zap.Error(err))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stream := jsonrpc2.NewStream(&CombinedReadWriteCloser{
|
||||
stdin: stdin,
|
||||
stdout: stdout,
|
||||
})
|
||||
|
||||
ctx, gopls.conn, gopls.server = protocol.NewClient(ctx, client, stream, logger)
|
||||
|
||||
initParams := protocol.InitializeParams{
|
||||
RootURI: uri.From("file", "", rootDir, "", ""),
|
||||
InitializationOptions: map[string]interface{}{
|
||||
"symbolMatcher": "FastFuzzy",
|
||||
},
|
||||
}
|
||||
|
||||
_, err = gopls.server.Initialize(ctx, &initParams)
|
||||
if err != nil {
|
||||
logger.Error("Error during initialize", zap.Error(err))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Step 2: Send 'initialized' notification
|
||||
err = gopls.server.Initialized(ctx, &protocol.InitializedParams{})
|
||||
if err != nil {
|
||||
logger.Error("Error during initialized", zap.Error(err))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return gopls
|
||||
}
|
||||
|
||||
func (gopls *Connection) Definition(ctx context.Context, filePath string, lineNumber int, charPosition int) (string, int, error) {
|
||||
// NOTE: gopls uses 0-based line and column numbers
|
||||
defFile, defLine, err := gopls.definition(ctx, filePath, lineNumber-1, charPosition-1)
|
||||
return defFile, defLine + 1, err
|
||||
}
|
||||
|
||||
func (gopls *Connection) definition(ctx context.Context, filePath string, lineNumber int, charPosition int) (string, int, error) {
|
||||
// Define the file URI and position where the function/method is invoked
|
||||
fileURI := protocol.DocumentURI("file://" + filePath) // Replace with actual file URI
|
||||
line := lineNumber // Line number where the function is called
|
||||
character := charPosition // Character (column) where the function is called
|
||||
|
||||
// Send the definition request
|
||||
params := &protocol.DefinitionParams{
|
||||
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
||||
TextDocument: protocol.TextDocumentIdentifier{
|
||||
URI: fileURI,
|
||||
},
|
||||
Position: protocol.Position{
|
||||
Line: uint32(line),
|
||||
Character: uint32(character),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create context with a timeout to avoid hanging
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
locations, err := gopls.server.Definition(ctx, params)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "failed to fetch definition")
|
||||
}
|
||||
|
||||
if len(locations) == 0 {
|
||||
return "", 0, errors.New("no definition found")
|
||||
}
|
||||
|
||||
location := locations[0]
|
||||
return location.URI.Filename(), int(location.Range.Start.Line), nil
|
||||
}
|
||||
|
||||
func (gopls *Connection) DidOpen(ctx context.Context, path string, content string, logger *zap.Logger) {
|
||||
err := gopls.server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
|
||||
TextDocument: protocol.TextDocumentItem{
|
||||
URI: protocol.DocumentURI(path),
|
||||
LanguageID: "go",
|
||||
Version: 1,
|
||||
Text: content,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("failed to call DidOpen", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (gopls *Connection) DidClose(ctx context.Context, path string, lgoger *zap.Logger) {
|
||||
err := gopls.server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{
|
||||
TextDocument: protocol.TextDocumentIdentifier{
|
||||
URI: protocol.DocumentURI(path),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
lgoger.Error("failed to call DidClose", zap.Error(err))
|
||||
}
|
||||
}
|
29
cmd/lint-panics/gopls/stream.go
Normal file
29
cmd/lint-panics/gopls/stream.go
Normal file
@ -0,0 +1,29 @@
|
||||
package gopls
|
||||
|
||||
import "io"
|
||||
|
||||
// CombinedReadWriteCloser combines stdin and stdout into one interface.
|
||||
type CombinedReadWriteCloser struct {
|
||||
stdin io.WriteCloser
|
||||
stdout io.ReadCloser
|
||||
}
|
||||
|
||||
// Write writes data to stdin.
|
||||
func (c *CombinedReadWriteCloser) Write(p []byte) (n int, err error) {
|
||||
return c.stdin.Write(p)
|
||||
}
|
||||
|
||||
// Read reads data from stdout.
|
||||
func (c *CombinedReadWriteCloser) Read(p []byte) (n int, err error) {
|
||||
return c.stdout.Read(p)
|
||||
}
|
||||
|
||||
// Close closes both stdin and stdout.
|
||||
func (c *CombinedReadWriteCloser) Close() error {
|
||||
err1 := c.stdin.Close()
|
||||
err2 := c.stdout.Close()
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
100
cmd/lint-panics/main.go
Normal file
100
cmd/lint-panics/main.go
Normal file
@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
gopls2 "github.com/status-im/status-go/cmd/lint-panics/gopls"
|
||||
"github.com/status-im/status-go/cmd/lint-panics/processor"
|
||||
"golang.org/x/tools/go/analysis/singlechecker"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"go/ast"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger := buildLogger()
|
||||
|
||||
if len(os.Args) == 0 {
|
||||
logger.Error("Usage: go run main.go <directory>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Dirty hack to get the root directory for gopls
|
||||
lastArg := os.Args[len(os.Args)-1]
|
||||
dir, err := getRootAbsolutePath(lastArg)
|
||||
if err != nil {
|
||||
logger.Error("failed to get root directory", zap.Error(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.Info("starting analysis...", zap.String("directory", dir))
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
gopls := gopls2.NewGoplsClient(ctx, logger, dir)
|
||||
|
||||
analyzer := &analysis.Analyzer{
|
||||
Name: "logpanics",
|
||||
Doc: "reports missing defer call to LogOnPanic",
|
||||
Run: func(pass *analysis.Pass) (interface{}, error) {
|
||||
for _, file := range pass.Files {
|
||||
p := processor.NewProcessor(logger, pass, gopls)
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
p.ProcessNode(ctx, n)
|
||||
return true
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
singlechecker.Main(analyzer)
|
||||
}
|
||||
|
||||
func buildLogger() *zap.Logger {
|
||||
// Initialize logger with colors
|
||||
loggerConfig := zap.NewDevelopmentConfig()
|
||||
loggerConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
loggerConfig.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
|
||||
loggerConfig.Development = false
|
||||
loggerConfig.DisableStacktrace = true
|
||||
logger, err := loggerConfig.Build()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to initialize logger: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return logger.Named("main")
|
||||
}
|
||||
|
||||
func getRootAbsolutePath(path string) (string, error) {
|
||||
// Get the absolute path of the current working directory
|
||||
workingDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get working directory: %w", err)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, "...") {
|
||||
path = strings.TrimSuffix(path, "...")
|
||||
}
|
||||
|
||||
// Check if the given path is absolute
|
||||
if !filepath.IsAbs(path) {
|
||||
// If the path is not absolute, join it with the working directory
|
||||
path = filepath.Join(workingDir, path)
|
||||
}
|
||||
|
||||
// Convert the path to an absolute path (cleans the result)
|
||||
absolutePath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to convert to absolute path: %w", err)
|
||||
}
|
||||
|
||||
return absolutePath, nil
|
||||
}
|
46
cmd/lint-panics/main_test.go
Normal file
46
cmd/lint-panics/main_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
gopls2 "github.com/status-im/status-go/cmd/lint-panics/gopls"
|
||||
"github.com/status-im/status-go/cmd/lint-panics/processor"
|
||||
"time"
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"runtime"
|
||||
"path"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
loggerConfig := zap.NewDevelopmentConfig()
|
||||
loggerConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
|
||||
loggerConfig.Development = false
|
||||
loggerConfig.DisableStacktrace = true
|
||||
logger := zap.Must(loggerConfig.Build())
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
_, filePath, _, _ := runtime.Caller(0)
|
||||
testDir := path.Join(path.Dir(filePath), "test")
|
||||
testFie := path.Join(testDir, "test.go")
|
||||
|
||||
gopls := gopls2.NewGoplsClient(ctx, logger, testDir)
|
||||
parser := processor.NewProcessor(logger, gopls)
|
||||
|
||||
result, err := parser.Run(ctx, testFie)
|
||||
require.NoError(t, err)
|
||||
|
||||
paths := result.Paths()
|
||||
require.Len(t, paths, 5)
|
||||
|
||||
expectedPaths := []string{
|
||||
path.Join(testDir, "test.go:39"),
|
||||
path.Join(testDir, "test.go:47"),
|
||||
path.Join(testDir, "test.go:55"),
|
||||
path.Join(testDir, "test.go:63"),
|
||||
path.Join(testDir, "test.go:27"),
|
||||
}
|
||||
require.EqualValues(t, expectedPaths, paths)
|
||||
}
|
203
cmd/lint-panics/processor/processor.go
Normal file
203
cmd/lint-panics/processor/processor.go
Normal file
@ -0,0 +1,203 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"os"
|
||||
"go.uber.org/zap"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
gotoken "go/token"
|
||||
goparser "go/parser"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/status-im/status-go/cmd/lint-panics/utils"
|
||||
"context"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
const Pattern = "LogOnPanic"
|
||||
|
||||
type Processor struct {
|
||||
logger *zap.Logger
|
||||
lsp LSP
|
||||
pass *analysis.Pass
|
||||
}
|
||||
|
||||
type LSP interface {
|
||||
Definition(context.Context, string, int, int) (string, int, error)
|
||||
}
|
||||
|
||||
func NewProcessor(logger *zap.Logger, pass *analysis.Pass, lsp LSP) *Processor {
|
||||
return &Processor{
|
||||
logger: logger.Named("processor"),
|
||||
pass: pass,
|
||||
lsp: lsp,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) Run(ctx context.Context, path string) error {
|
||||
logger := p.logger.With(zap.String("file", path))
|
||||
|
||||
file, err := p.parseFile(path)
|
||||
if err != nil {
|
||||
logger.Error("failed to parse file", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// Traverse the AST to find goroutines
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
p.ProcessNode(ctx, n)
|
||||
return true
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Processor) ProcessNode(ctx context.Context, n ast.Node) {
|
||||
// Check if the node is a GoStmt (which represents a 'go' statement)
|
||||
goStmt, ok := n.(*ast.GoStmt)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch fun := goStmt.Call.Fun.(type) {
|
||||
case *ast.FuncLit:
|
||||
// anonymous function
|
||||
pos := p.pass.Fset.Position(fun.Pos())
|
||||
logger := p.logger.With(
|
||||
utils.ZapURI(pos.Filename, pos.Line),
|
||||
zap.Int("column", pos.Column),
|
||||
)
|
||||
|
||||
logger.Debug("found anonymous goroutine")
|
||||
if err := p.checkGoroutine(fun.Body); err != nil {
|
||||
p.logLinterError(fun.Pos(), err)
|
||||
p.pass.Reportf(fun.Pos(), "missing %s()", Pattern)
|
||||
}
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
// method call
|
||||
pos := p.pass.Fset.Position(fun.Sel.Pos())
|
||||
p.logger.Info("found method call as goroutine",
|
||||
zap.String("methodName", fun.Sel.Name),
|
||||
utils.ZapURI(pos.Filename, pos.Line),
|
||||
zap.Int("column", pos.Column),
|
||||
)
|
||||
|
||||
defPos, err := p.checkGoroutineDefinition(ctx, pos)
|
||||
if err != nil {
|
||||
p.logLinterError(defPos, err)
|
||||
p.pass.Reportf(defPos, "missing %s(), goroutine at %s", Pattern, utils.PositionURI(pos))
|
||||
}
|
||||
|
||||
case *ast.Ident:
|
||||
// function call
|
||||
pos := p.pass.Fset.Position(fun.Pos())
|
||||
p.logger.Info("found function call as goroutine",
|
||||
zap.String("functionName", fun.Name),
|
||||
utils.ZapURI(pos.Filename, pos.Line),
|
||||
zap.Int("column", pos.Column),
|
||||
)
|
||||
|
||||
defPos, err := p.checkGoroutineDefinition(ctx, pos)
|
||||
if err != nil {
|
||||
p.logLinterError(defPos, err)
|
||||
p.pass.Reportf(defPos, "missing %s(), called as goroutine at %s", Pattern, utils.PositionURI(pos))
|
||||
}
|
||||
|
||||
default:
|
||||
p.logger.Error("unexpected goroutine type",
|
||||
zap.String("type", fmt.Sprintf("%T", fun)),
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Processor) parseFile(path string) (*ast.File, error) {
|
||||
logger := p.logger.With(zap.String("path", path))
|
||||
|
||||
src, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
logger.Error("failed to open file", zap.Error(err))
|
||||
}
|
||||
|
||||
file, err := goparser.ParseFile(p.pass.Fset, path, src, 0)
|
||||
if err != nil {
|
||||
logger.Error("failed to parse file", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (p *Processor) checkGoroutine(body *ast.BlockStmt) error {
|
||||
if body == nil {
|
||||
p.logger.Warn("missing function body")
|
||||
return nil
|
||||
}
|
||||
if len(body.List) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
deferStatement, ok := body.List[0].(*ast.DeferStmt)
|
||||
if !ok {
|
||||
return errors.New("first statement is not defer")
|
||||
}
|
||||
|
||||
selectorExpr, ok := deferStatement.Call.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return errors.New("first statement call is not a selector")
|
||||
}
|
||||
|
||||
firstLineFunName := selectorExpr.Sel.Name
|
||||
if firstLineFunName != Pattern {
|
||||
return errors.Errorf("first statement is not %s", Pattern)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Processor) getFunctionBody(node ast.Node, lineNumber int) (body *ast.BlockStmt, pos gotoken.Pos) {
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
// Check if the node is a function declaration
|
||||
funcDecl, ok := n.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if p.pass.Fset.Position(n.Pos()).Line != lineNumber {
|
||||
return true
|
||||
}
|
||||
|
||||
body = funcDecl.Body
|
||||
pos = n.Pos()
|
||||
return false
|
||||
})
|
||||
|
||||
return body, pos
|
||||
|
||||
}
|
||||
|
||||
func (p *Processor) checkGoroutineDefinition(ctx context.Context, pos gotoken.Position) (gotoken.Pos, error) {
|
||||
defFilePath, defLineNumber, err := p.lsp.Definition(ctx, pos.Filename, pos.Line, pos.Column)
|
||||
if err != nil {
|
||||
p.logger.Error("failed to find function definition", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
file, err := p.parseFile(defFilePath)
|
||||
if err != nil {
|
||||
p.logger.Error("failed to parse file", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
body, defPosition := p.getFunctionBody(file, defLineNumber)
|
||||
return defPosition, p.checkGoroutine(body)
|
||||
}
|
||||
|
||||
func (p *Processor) logLinterError(pos gotoken.Pos, err error) {
|
||||
position := p.pass.Fset.Position(pos)
|
||||
message := fmt.Sprintf("missing %s()", Pattern)
|
||||
p.logger.Warn(message,
|
||||
utils.ZapURI(position.Filename, position.Line),
|
||||
zap.String("details", err.Error()))
|
||||
}
|
65
cmd/lint-panics/test/goroutine_test.go
Normal file
65
cmd/lint-panics/test/goroutine_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
gocommon "github.com/status-im/status-go/common"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Test struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
t := Test{}
|
||||
go t.Empty()
|
||||
go t.Foo()
|
||||
go t.FooOK()
|
||||
go t.FooNotDefer()
|
||||
|
||||
go Empty()
|
||||
go Bar()
|
||||
go BarOK()
|
||||
go BarNotDefer()
|
||||
|
||||
go func() {
|
||||
defer gocommon.LogOnPanic()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
fmt.Println("anon")
|
||||
}()
|
||||
|
||||
go func() {}()
|
||||
|
||||
}
|
||||
|
||||
func (p *Test) Empty() {
|
||||
|
||||
}
|
||||
|
||||
func (p *Test) Foo() {
|
||||
defer fmt.Println("Foo")
|
||||
}
|
||||
|
||||
func (p *Test) FooOK() {
|
||||
defer gocommon.LogOnPanic()
|
||||
}
|
||||
|
||||
func (p *Test) FooNotDefer() {
|
||||
gocommon.LogOnPanic()
|
||||
}
|
||||
|
||||
func Empty() {
|
||||
|
||||
}
|
||||
|
||||
func Bar() {
|
||||
defer fmt.Println("Bar")
|
||||
}
|
||||
|
||||
func BarOK() {
|
||||
defer gocommon.LogOnPanic()
|
||||
}
|
||||
|
||||
func BarNotDefer() {
|
||||
gocommon.LogOnPanic()
|
||||
}
|
24
cmd/lint-panics/utils/utils.go
Normal file
24
cmd/lint-panics/utils/utils.go
Normal file
@ -0,0 +1,24 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"go.uber.org/zap"
|
||||
gotoken "go/token"
|
||||
)
|
||||
|
||||
func PositionURI(pos gotoken.Position) string {
|
||||
return URI(pos.Filename, pos.Line)
|
||||
}
|
||||
|
||||
func URI(path string, line int) string {
|
||||
return path + ":" + strconv.Itoa(line)
|
||||
}
|
||||
|
||||
func ZapURI(path string, line int) zap.Field {
|
||||
return zap.Field{
|
||||
Type: zapcore.StringType,
|
||||
Key: "uri",
|
||||
String: URI(path, line),
|
||||
}
|
||||
}
|
6
go.mod
6
go.mod
@ -99,6 +99,9 @@ require (
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.7
|
||||
github.com/yeqown/go-qrcode/v2 v2.2.1
|
||||
github.com/yeqown/go-qrcode/writer/standard v1.2.1
|
||||
go.lsp.dev/jsonrpc2 v0.10.0
|
||||
go.lsp.dev/protocol v0.12.0
|
||||
go.lsp.dev/uri v0.3.0
|
||||
go.uber.org/mock v0.4.0
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
|
||||
@ -253,6 +256,8 @@ require (
|
||||
github.com/russolsen/ohyeah v0.0.0-20160324131710-f4938c005315 // indirect
|
||||
github.com/russolsen/same v0.0.0-20160222130632-f089df61f51d // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/segmentio/asm v1.1.3 // indirect
|
||||
github.com/segmentio/encoding v0.3.4 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
@ -275,6 +280,7 @@ require (
|
||||
github.com/yeqown/reedsolomon v1.0.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/dig v1.18.0 // indirect
|
||||
go.uber.org/fx v1.22.2 // indirect
|
||||
|
13
go.sum
13
go.sum
@ -1936,6 +1936,10 @@ github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
|
||||
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
|
||||
github.com/segmentio/encoding v0.3.4 h1:WM4IBnxH8B9TakiM2QD5LyNl9JSndh88QbHqVC+Pauc=
|
||||
github.com/segmentio/encoding v0.3.4/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM=
|
||||
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
@ -2221,6 +2225,14 @@ go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lL
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
|
||||
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
|
||||
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
|
||||
go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI=
|
||||
go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac=
|
||||
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE=
|
||||
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2/go.mod h1:gtSHRuYfbCT0qnbLnovpie/WEmqyJ7T4n6VXiFMBtcw=
|
||||
go.lsp.dev/protocol v0.12.0 h1:tNprUI9klQW5FAFVM4Sa+AbPFuVQByWhP1ttNUAjIWg=
|
||||
go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ=
|
||||
go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo=
|
||||
go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
|
||||
go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.7.0/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8=
|
||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
@ -2677,6 +2689,7 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
Loading…
x
Reference in New Issue
Block a user