From 18d8083de350ef07161b7ceb5812617337a0c4a3 Mon Sep 17 00:00:00 2001 From: John Mulligan Date: Mon, 4 May 2020 15:00:53 -0400 Subject: [PATCH] implements: add initial code for process Go sources This code makes use of Go's native AST processing packages and allows us to analyze our source code using the go-ceph conventions. Signed-off-by: John Mulligan --- .../implements/internal/implements/gosrc.go | 124 ++++++++++++++++++ .../internal/implements/inspector.go | 64 +++++++++ 2 files changed, 188 insertions(+) create mode 100644 contrib/implements/internal/implements/gosrc.go create mode 100644 contrib/implements/internal/implements/inspector.go diff --git a/contrib/implements/internal/implements/gosrc.go b/contrib/implements/internal/implements/gosrc.go new file mode 100644 index 0000000..f73ccea --- /dev/null +++ b/contrib/implements/internal/implements/gosrc.go @@ -0,0 +1,124 @@ +package implements + +import ( + "go/ast" + "go/build" + "go/parser" + "go/token" + "io/ioutil" + "path" + "regexp" + "strings" +) + +type visitor struct { + inFunction *ast.FuncDecl + + callMap map[string]string + docMap map[string]string +} + +func newVisitor() *visitor { + return &visitor{ + callMap: map[string]string{}, + docMap: map[string]string{}, + } +} + +func (v *visitor) checkDocImplements(fdec *ast.FuncDecl) { + dtext := fdec.Doc.Text() + lines := strings.Split(dtext, "\n") + for i := range lines { + if lines[i] == "Implements:" { + cfunc := cfuncFromComment(lines[i+1]) + if cfunc == "" { + return + } + v.docMap[cfunc] = fdec.Name.Name + logger.Printf("updated %s in doc map\n", cfunc) + } + } +} + +func (v *visitor) checkCalled(s *ast.SelectorExpr) { + ident, ok := s.X.(*ast.Ident) + if !ok { + return + } + if "C" == ident.String() { + v.callMap[s.Sel.String()] = v.inFunction.Name.Name + logger.Printf("updated %s in call map\n", s.Sel.String()) + } +} + +func (v *visitor) Visit(node ast.Node) ast.Visitor { + switch { + case node == nil: + return nil + case v.inFunction == nil: + case node.Pos() > v.inFunction.End(): + logger.Printf("left function %v\n", v.inFunction.Name.Name) + v.inFunction = nil + } + + switch n := node.(type) { + case *ast.File: + v.inFunction = nil + return v + case *ast.FuncDecl: + logger.Printf("checking function: %v\n", n.Name.Name) + v.checkDocImplements(n) + v.inFunction = n + return v + case *ast.CallExpr: + if v.inFunction == nil { + return nil + } + if s, ok := n.Fun.(*ast.SelectorExpr); ok { + v.checkCalled(s) + } + } + if v.inFunction != nil { + return v + } + return nil +} + +func cfuncFromComment(ctext string) string { + m := regexp.MustCompile(` ([a-zA-Z0-9_]+)\(`).FindAllSubmatch([]byte(ctext), 1) + if len(m) < 1 { + return "" + } + return string(m[0][1]) +} + +// CephGoFunctions will look for C functions called by the code code and +// update the found functions for the package within the inspector. +func CephGoFunctions(source, packageName string, ii *Inspector) error { + p, err := build.Import("./"+packageName, source, 0) + if err != nil { + return err + } + + toCheck := []string{} + toCheck = append(toCheck, p.GoFiles...) + toCheck = append(toCheck, p.CgoFiles...) + for _, fname := range toCheck { + logger.Printf("Reading go file: %v\n", fname) + src, err := ioutil.ReadFile(path.Join(p.Dir, fname)) + if err != nil { + return err + } + fset := token.NewFileSet() + f, err := parser.ParseFile( + fset, + fname, + src, + parser.ParseComments|parser.AllErrors) + if err != nil { + return err + } + ast.Walk(ii.visitor, f) + } + return nil +} diff --git a/contrib/implements/internal/implements/inspector.go b/contrib/implements/internal/implements/inspector.go new file mode 100644 index 0000000..147f924 --- /dev/null +++ b/contrib/implements/internal/implements/inspector.go @@ -0,0 +1,64 @@ +package implements + +import ( + "strings" +) + +type foundFlags int + +const ( + isCalled = foundFlags(1) + isDocumented = foundFlags(2) + isDeprecated = foundFlags(4) +) + +// Inspector types collect the high-level results from C and Go +// code scans. +type Inspector struct { + visitor *visitor + + expected CFunctions + found map[string]foundFlags + deprecatedMissing int +} + +// SetExpected sets the expected C functions, asuming the supplied prefix. +func (ii *Inspector) SetExpected(prefix string, expected CFunctions) error { + ii.expected = make([]CFunction, 0, len(expected)) + for _, cfunc := range expected { + if strings.HasPrefix(cfunc.Name, prefix) { + logger.Printf("C function \"%s\" has matching prefix", cfunc.Name) + ii.expected = append(ii.expected, cfunc) + } + } + _, err := ii.expected.ensure() + return err +} + +func (ii *Inspector) update() { + ii.found = map[string]foundFlags{} + ii.deprecatedMissing = 0 + for i := range ii.expected { + n := ii.expected[i].Name + if _, found := ii.visitor.callMap[n]; found { + ii.found[n] |= isCalled + } + if _, found := ii.visitor.docMap[n]; found { + ii.found[n] |= isDocumented + } + if ii.expected[i].isDeprecated() { + if _, found := ii.found[n]; found { + ii.found[n] |= isDeprecated + } else { + ii.deprecatedMissing++ + } + } + } +} + +// NewInspector returns a newly created code inspector object. +func NewInspector() *Inspector { + return &Inspector{ + visitor: newVisitor(), + } +}