src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

validate.go (3171B)


      1 // Copyright 2018 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package analysis
      6 
      7 import (
      8 	"fmt"
      9 	"reflect"
     10 	"strings"
     11 	"unicode"
     12 )
     13 
     14 // Validate reports an error if any of the analyzers are misconfigured.
     15 // Checks include:
     16 // that the name is a valid identifier;
     17 // that the Doc is not empty;
     18 // that the Run is non-nil;
     19 // that the Requires graph is acyclic;
     20 // that analyzer fact types are unique;
     21 // that each fact type is a pointer.
     22 //
     23 // Analyzer names need not be unique, though this may be confusing.
     24 func Validate(analyzers []*Analyzer) error {
     25 	// Map each fact type to its sole generating analyzer.
     26 	factTypes := make(map[reflect.Type]*Analyzer)
     27 
     28 	// Traverse the Requires graph, depth first.
     29 	const (
     30 		white = iota
     31 		grey
     32 		black
     33 		finished
     34 	)
     35 	color := make(map[*Analyzer]uint8)
     36 	var visit func(a *Analyzer) error
     37 	visit = func(a *Analyzer) error {
     38 		if a == nil {
     39 			return fmt.Errorf("nil *Analyzer")
     40 		}
     41 		if color[a] == white {
     42 			color[a] = grey
     43 
     44 			// names
     45 			if !validIdent(a.Name) {
     46 				return fmt.Errorf("invalid analyzer name %q", a)
     47 			}
     48 
     49 			if a.Doc == "" {
     50 				return fmt.Errorf("analyzer %q is undocumented", a)
     51 			}
     52 
     53 			if a.Run == nil {
     54 				return fmt.Errorf("analyzer %q has nil Run", a)
     55 			}
     56 			// fact types
     57 			for _, f := range a.FactTypes {
     58 				if f == nil {
     59 					return fmt.Errorf("analyzer %s has nil FactType", a)
     60 				}
     61 				t := reflect.TypeOf(f)
     62 				if prev := factTypes[t]; prev != nil {
     63 					return fmt.Errorf("fact type %s registered by two analyzers: %v, %v",
     64 						t, a, prev)
     65 				}
     66 				if t.Kind() != reflect.Pointer {
     67 					return fmt.Errorf("%s: fact type %s is not a pointer", a, t)
     68 				}
     69 				factTypes[t] = a
     70 			}
     71 
     72 			// recursion
     73 			for _, req := range a.Requires {
     74 				if err := visit(req); err != nil {
     75 					return err
     76 				}
     77 			}
     78 			color[a] = black
     79 		}
     80 
     81 		if color[a] == grey {
     82 			stack := []*Analyzer{a}
     83 			inCycle := map[string]bool{}
     84 			for len(stack) > 0 {
     85 				current := stack[len(stack)-1]
     86 				stack = stack[:len(stack)-1]
     87 				if color[current] == grey && !inCycle[current.Name] {
     88 					inCycle[current.Name] = true
     89 					stack = append(stack, current.Requires...)
     90 				}
     91 			}
     92 			return &CycleInRequiresGraphError{AnalyzerNames: inCycle}
     93 		}
     94 
     95 		return nil
     96 	}
     97 	for _, a := range analyzers {
     98 		if err := visit(a); err != nil {
     99 			return err
    100 		}
    101 	}
    102 
    103 	// Reject duplicates among analyzers.
    104 	// Precondition:  color[a] == black.
    105 	// Postcondition: color[a] == finished.
    106 	for _, a := range analyzers {
    107 		if color[a] == finished {
    108 			return fmt.Errorf("duplicate analyzer: %s", a.Name)
    109 		}
    110 		color[a] = finished
    111 	}
    112 
    113 	return nil
    114 }
    115 
    116 func validIdent(name string) bool {
    117 	for i, r := range name {
    118 		if !(r == '_' || unicode.IsLetter(r) || i > 0 && unicode.IsDigit(r)) {
    119 			return false
    120 		}
    121 	}
    122 	return name != ""
    123 }
    124 
    125 type CycleInRequiresGraphError struct {
    126 	AnalyzerNames map[string]bool
    127 }
    128 
    129 func (e *CycleInRequiresGraphError) Error() string {
    130 	var b strings.Builder
    131 	b.WriteString("cycle detected involving the following analyzers:")
    132 	for n := range e.AnalyzerNames {
    133 		b.WriteByte(' ')
    134 		b.WriteString(n)
    135 	}
    136 	return b.String()
    137 }