// +build ignore package main import ( "bytes" "fmt" "go/ast" "go/build" "go/parser" "go/printer" "go/token" "log" "os" "path/filepath" "strings" ) var ( fset = token.NewFileSet() ) func main() { log.SetFlags(0) bpkg, err := build.ImportDir(".", 0) if err != nil { log.Fatal(err) } pkgs, err := parser.ParseDir(fset, bpkg.Dir, func(fi os.FileInfo) bool { return filepath.Ext(fi.Name()) == ".go" }, 0) if err != nil { log.Fatal(err) } for _, pkg := range pkgs { if err := checkPkg(pkg); err != nil { log.Fatal(err) } } if len(pkgs) == 0 { log.Fatal("No packages to check.") } } var ignoreViolationsInFunc = map[string]bool{ "MakeGitError": true, "MakeGitError2": true, } func checkPkg(pkg *ast.Package) error { var violations []string ast.Inspect(pkg, func(node ast.Node) bool { switch node := node.(type) { case *ast.FuncDecl: var b bytes.Buffer if err := printer.Fprint(&b, fset, node); err != nil { log.Fatal(err) } src := b.String() if strings.Contains(src, "MakeGitError") && !strings.Contains(src, "runtime.LockOSThread()") && !strings.Contains(src, "defer runtime.UnlockOSThread()") && !ignoreViolationsInFunc[node.Name.Name] { pos := fset.Position(node.Pos()) violations = append(violations, fmt.Sprintf("%s at %s:%d", node.Name.Name, pos.Filename, pos.Line)) } } return true }) if len(violations) > 0 { return fmt.Errorf("%d non-thread-locked calls to MakeGitError found. To fix, add the following to each func below that calls MakeGitError, before the cgo call that might produce the error:\n\n\truntime.LockOSThread()\n\tdefer runtime.UnlockOSThread()\n\n%s", len(violations), strings.Join(violations, "\n")) } return nil }