182 lines
4.6 KiB
Go
182 lines
4.6 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
//go:generate go run check-monitoring.go ../monkit.lock
|
|
|
|
// +build ignore
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/packages"
|
|
)
|
|
|
|
var (
|
|
monkitPath = "gopkg.in/spacemonkeygo/monkit.v2"
|
|
lockFilePerms = os.FileMode(0644)
|
|
)
|
|
|
|
func main() {
|
|
pkgs, err := packages.Load(&packages.Config{
|
|
Mode: packages.NeedCompiledGoFiles | packages.NeedSyntax | packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo,
|
|
}, "storj.io/storj/...")
|
|
if err != nil {
|
|
log.Fatalf("error while loading packages: %s", err)
|
|
}
|
|
|
|
var lockedFnNames []string
|
|
for _, pkg := range pkgs {
|
|
lockedFnNames = append(lockedFnNames, findLockedFnNames(pkg)...)
|
|
}
|
|
sortedNames := sortAndUnique(lockedFnNames)
|
|
|
|
outputStr := strings.Join(sortedNames, "\n")
|
|
if len(os.Args) == 2 {
|
|
file := os.Args[1]
|
|
if err := ioutil.WriteFile(file, []byte(outputStr+"\n"), lockFilePerms); err != nil {
|
|
log.Fatalf("error while writing to file \"%s\": %s", file, err)
|
|
}
|
|
} else {
|
|
fmt.Println(outputStr)
|
|
}
|
|
}
|
|
|
|
func findLockedFnNames(pkg *packages.Package) []string {
|
|
var (
|
|
lockedTasksPos []token.Pos
|
|
lockedTaskFns []*ast.FuncDecl
|
|
lockedFnInfos []string
|
|
)
|
|
|
|
// Collect locked comments and what line they are on.
|
|
for _, file := range pkg.Syntax {
|
|
lockedLines := make(map[int]struct{})
|
|
for _, group := range file.Comments {
|
|
for _, comment := range group.List {
|
|
if comment.Text == "//locked" {
|
|
commentLine := pkg.Fset.Position(comment.Pos()).Line
|
|
lockedLines[commentLine] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
if len(lockedLines) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Find calls to monkit functions we're interested in that are on the
|
|
// same line as a "locked" comment and keep track of their position.
|
|
// NB: always return true to walk entire node tree.
|
|
ast.Inspect(file, func(node ast.Node) bool {
|
|
if node == nil {
|
|
return true
|
|
}
|
|
if !isMonkitCall(pkg, node) {
|
|
return true
|
|
}
|
|
|
|
// Ensure call line matches a "locked" comment line.
|
|
callLine := pkg.Fset.Position(node.End()).Line
|
|
if _, ok := lockedLines[callLine]; !ok {
|
|
return true
|
|
}
|
|
|
|
// We are already checking to ensure that these type assertions are valid in `isMonkitCall`.
|
|
sel := node.(*ast.CallExpr).Fun.(*ast.SelectorExpr)
|
|
|
|
// Track `mon.Task` calls.
|
|
if sel.Sel.Name == "Task" {
|
|
lockedTasksPos = append(lockedTasksPos, node.End())
|
|
return true
|
|
}
|
|
|
|
// Track other monkit calls that have one string argument (e.g. monkit.FloatVal, etc.)
|
|
// and transform them to representative string.
|
|
if len(node.(*ast.CallExpr).Args) != 1 {
|
|
return true
|
|
}
|
|
argLiteral, ok := node.(*ast.CallExpr).Args[0].(*ast.BasicLit)
|
|
if !ok {
|
|
return true
|
|
}
|
|
if argLiteral.Kind == token.STRING {
|
|
lockedFnInfo := pkg.PkgPath + "." + argLiteral.Value + " " + sel.Sel.Name
|
|
lockedFnInfos = append(lockedFnInfos, lockedFnInfo)
|
|
}
|
|
return true
|
|
})
|
|
|
|
// Track all function declarations containing locked `mon.Task` calls.
|
|
ast.Inspect(file, func(node ast.Node) bool {
|
|
fn, ok := node.(*ast.FuncDecl)
|
|
if !ok {
|
|
return true
|
|
}
|
|
for _, locked := range lockedTasksPos {
|
|
if fn.Pos() < locked && locked < fn.End() {
|
|
lockedTaskFns = append(lockedTaskFns, fn)
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
}
|
|
|
|
// Transform the ast.FuncDecls containing locked `mon.Task` calls to representative string.
|
|
for _, fn := range lockedTaskFns {
|
|
object := pkg.TypesInfo.Defs[fn.Name]
|
|
|
|
var receiver string
|
|
if fn.Recv != nil {
|
|
typ := fn.Recv.List[0].Type
|
|
if star, ok := typ.(*ast.StarExpr); ok {
|
|
receiver = ".*"
|
|
typ = star.X
|
|
} else {
|
|
receiver = "."
|
|
}
|
|
recvObj := pkg.TypesInfo.Uses[typ.(*ast.Ident)]
|
|
receiver += recvObj.Name()
|
|
}
|
|
|
|
lockedFnInfo := object.Pkg().Path() + receiver + "." + object.Name() + " Task"
|
|
lockedFnInfos = append(lockedFnInfos, lockedFnInfo)
|
|
|
|
}
|
|
return lockedFnInfos
|
|
}
|
|
|
|
// isMonkitCall returns whether the node is a call to a function in the monkit package.
|
|
func isMonkitCall(pkg *packages.Package, in ast.Node) bool {
|
|
defer func() { recover() }()
|
|
|
|
ident := in.(*ast.CallExpr).Fun.(*ast.SelectorExpr).X.(*ast.Ident)
|
|
|
|
return pkg.TypesInfo.Uses[ident].(*types.Var).Type().(*types.Pointer).Elem().(*types.Named).Obj().Pkg().Path() == monkitPath
|
|
}
|
|
|
|
func sortAndUnique(input []string) (unique []string) {
|
|
set := make(map[string]struct{})
|
|
for _, item := range input {
|
|
if _, ok := set[item]; ok {
|
|
continue
|
|
} else {
|
|
set[item] = struct{}{}
|
|
}
|
|
}
|
|
for item := range set {
|
|
unique = append(unique, item)
|
|
}
|
|
sort.Strings(unique)
|
|
return unique
|
|
}
|