d1353e1f7c
* update code.gitea.io/sdk/gitea v0.13.1 -> v0.13.2 * update github.com/go-swagger/go-swagger v0.25.0 -> v0.26.0 * update github.com/google/uuid v1.1.2 -> v1.2.0 * update github.com/klauspost/compress v1.11.3 -> v1.11.7 * update github.com/lib/pq 083382b7e6fc -> v1.9.0 * update github.com/markbates/goth v1.65.0 -> v1.66.1 * update github.com/mattn/go-sqlite3 v1.14.4 -> v1.14.6 * update github.com/mgechev/revive 246eac737dc7 -> v1.0.3 * update github.com/minio/minio-go/v7 v7.0.6 -> v7.0.7 * update github.com/niklasfasching/go-org v1.3.2 -> v1.4.0 * update github.com/olivere/elastic/v7 v7.0.21 -> v7.0.22 * update github.com/pquerna/otp v1.2.0 -> v1.3.0 * update github.com/xanzy/go-gitlab v0.39.0 -> v0.42.0 * update github.com/yuin/goldmark v1.2.1 -> v1.3.1
131 lines
2.9 KiB
Go
Vendored
131 lines
2.9 KiB
Go
Vendored
// Copyright 2018 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
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()
|
|
}
|