| package analysis | |
| import ( | |
| "fmt" | |
| "reflect" | |
| "strings" | |
| "unicode" | |
| ) | |
| // Validate reports an error if any of the analyzers are misconfigured. | |
| // Checks include: | |
| // that the name is a valid identifier; | |
| // that the Requires graph is acyclic; | |
| // that analyzer fact types are unique; | |
| // that each fact type is a pointer. | |
| func Validate(analyzers []*Analyzer) error { | |
| // Map each fact type to its sole generating analyzer. | |
| factTypes := make(map[reflect.Type]*Analyzer) | |
| // Traverse the Requires graph, depth first. | |
| const ( | |
| white = iota | |
| grey | |
| black | |
| finished | |
| ) | |
| color := make(map[*Analyzer]uint8) | |
| var visit func(a *Analyzer) error | |
| visit = func(a *Analyzer) error { | |
| if a == nil { | |
| return fmt.Errorf("nil *Analyzer") | |
| } | |
| if color[a] == white { | |
| color[a] = grey | |
| // names | |
| if !validIdent(a.Name) { | |
| return fmt.Errorf("invalid analyzer name %q", a) | |
| } | |
| if a.Doc == "" { | |
| return fmt.Errorf("analyzer %q is undocumented", a) | |
| } | |
| // fact types | |
| for _, f := range a.FactTypes { | |
| if f == nil { | |
| return fmt.Errorf("analyzer %s has nil FactType", a) | |
| } | |
| t := reflect.TypeOf(f) | |
| if prev := factTypes[t]; prev != nil { | |
| return fmt.Errorf("fact type %s registered by two analyzers: %v, %v", | |
| t, a, prev) | |
| } | |
| if t.Kind() != reflect.Ptr { | |
| return fmt.Errorf("%s: fact type %s is not a pointer", a, t) | |
| } | |
| factTypes[t] = a | |
| } | |
| // recursion | |
| for _, req := range a.Requires { | |
| if err := visit(req); err != nil { | |
| return err | |
| } | |
| } | |
| color[a] = black | |
| } | |
| if color[a] == grey { | |
| stack := []*Analyzer{a} | |
| inCycle := map[string]bool{} | |
| for len(stack) > 0 { | |
| current := stack[len(stack)-1] | |
| stack = stack[:len(stack)-1] | |
| if color[current] == grey && !inCycle[current.Name] { | |
| inCycle[current.Name] = true | |
| stack = append(stack, current.Requires...) | |
| } | |
| } | |
| return &CycleInRequiresGraphError{AnalyzerNames: inCycle} | |
| } | |
| return nil | |
| } | |
| for _, a := range analyzers { | |
| if err := visit(a); err != nil { | |
| return err | |
| } | |
| } | |
| // Reject duplicates among analyzers. | |
| // Precondition: color[a] == black. | |
| // Postcondition: color[a] == finished. | |
| for _, a := range analyzers { | |
| if color[a] == finished { | |
| return fmt.Errorf("duplicate analyzer: %s", a.Name) | |
| } | |
| color[a] = finished | |
| } | |
| return nil | |
| } | |
| func validIdent(name string) bool { | |
| for i, r := range name { | |
| if !(r == '_' || unicode.IsLetter(r) || i > 0 && unicode.IsDigit(r)) { | |
| return false | |
| } | |
| } | |
| return name != "" | |
| } | |
| type CycleInRequiresGraphError struct { | |
| AnalyzerNames map[string]bool | |
| } | |
| func (e *CycleInRequiresGraphError) Error() string { | |
| var b strings.Builder | |
| b.WriteString("cycle detected involving the following analyzers:") | |
| for n := range e.AnalyzerNames { | |
| b.WriteByte(' ') | |
| b.WriteString(n) | |
| } | |
| return b.String() | |
| } |