storj/scripts/check-monitoring.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
}