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 <jmulligan@redhat.com>
This commit is contained in:
John Mulligan 2020-05-04 15:00:53 -04:00 committed by John Mulligan
parent ce7911ea35
commit 18d8083de3
2 changed files with 188 additions and 0 deletions

View File

@ -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
}

View File

@ -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(),
}
}