blob: 4f2c404562292236ad91de82ac87eac1ab30e083 [file] [log] [blame]
Rebecca Stambler92778472021-01-05 23:05:35 -05001// 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
Alan Donovan9f3b32b2018-09-17 09:25:48 -04005package analysis
6
7import (
8 "fmt"
Alan Donovan308f0c72018-09-26 16:34:31 -04009 "reflect"
Michaël Lévesque-Dion6422fca2020-08-10 19:12:37 +000010 "strings"
Alan Donovan9f3b32b2018-09-17 09:25:48 -040011 "unicode"
12)
13
Alan Donovane2857122018-09-24 17:32:33 -040014// Validate reports an error if any of the analyzers are misconfigured.
Alan Donovan9f3b32b2018-09-17 09:25:48 -040015// Checks include:
Alan Donovan4601f5d2018-10-04 13:56:41 -040016// that the name is a valid identifier;
zhangyunhao9d8009b2021-06-20 22:57:11 +080017// that the Doc is not empty;
18// that the Run is non-nil;
Ainar Garipov5b82db02019-09-08 19:10:01 +030019// that the Requires graph is acyclic;
Alan Donovan4601f5d2018-10-04 13:56:41 -040020// that analyzer fact types are unique;
21// that each fact type is a pointer.
Alan Donovan28813182023-11-01 14:13:49 -040022//
23// Analyzer names need not be unique, though this may be confusing.
Alan Donovane2857122018-09-24 17:32:33 -040024func Validate(analyzers []*Analyzer) error {
Alan Donovan308f0c72018-09-26 16:34:31 -040025 // Map each fact type to its sole generating analyzer.
26 factTypes := make(map[reflect.Type]*Analyzer)
27
Alan Donovan9f3b32b2018-09-17 09:25:48 -040028 // Traverse the Requires graph, depth first.
Alan Donovan8149dec2018-10-10 14:27:22 -040029 const (
30 white = iota
31 grey
32 black
33 finished
34 )
35 color := make(map[*Analyzer]uint8)
Alan Donovane2857122018-09-24 17:32:33 -040036 var visit func(a *Analyzer) error
37 visit = func(a *Analyzer) error {
Alan Donovan9f3b32b2018-09-17 09:25:48 -040038 if a == nil {
Alan Donovan308f0c72018-09-26 16:34:31 -040039 return fmt.Errorf("nil *Analyzer")
Alan Donovan9f3b32b2018-09-17 09:25:48 -040040 }
Alan Donovan8149dec2018-10-10 14:27:22 -040041 if color[a] == white {
42 color[a] = grey
Alan Donovan9f3b32b2018-09-17 09:25:48 -040043
44 // names
45 if !validIdent(a.Name) {
Alan Donovan308f0c72018-09-26 16:34:31 -040046 return fmt.Errorf("invalid analyzer name %q", a)
Alan Donovan9f3b32b2018-09-17 09:25:48 -040047 }
Alan Donovan9f3b32b2018-09-17 09:25:48 -040048
49 if a.Doc == "" {
Alan Donovan308f0c72018-09-26 16:34:31 -040050 return fmt.Errorf("analyzer %q is undocumented", a)
51 }
52
zhangyunhao9d8009b2021-06-20 22:57:11 +080053 if a.Run == nil {
54 return fmt.Errorf("analyzer %q has nil Run", a)
55 }
Alan Donovan308f0c72018-09-26 16:34:31 -040056 // 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.Ptr {
67 return fmt.Errorf("%s: fact type %s is not a pointer", a, t)
68 }
69 factTypes[t] = a
Alan Donovan9f3b32b2018-09-17 09:25:48 -040070 }
71
Alan Donovan9f3b32b2018-09-17 09:25:48 -040072 // recursion
Michaël Lévesque-Dion6422fca2020-08-10 19:12:37 +000073 for _, req := range a.Requires {
Alan Donovan9f3b32b2018-09-17 09:25:48 -040074 if err := visit(req); err != nil {
Michaël Lévesque-Dion6422fca2020-08-10 19:12:37 +000075 return err
Alan Donovan9f3b32b2018-09-17 09:25:48 -040076 }
77 }
Alan Donovan8149dec2018-10-10 14:27:22 -040078 color[a] = black
Alan Donovan9f3b32b2018-09-17 09:25:48 -040079 }
80
Michaël Lévesque-Dion6422fca2020-08-10 19:12:37 +000081 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
Alan Donovan9f3b32b2018-09-17 09:25:48 -040095 return nil
96 }
Alan Donovane2857122018-09-24 17:32:33 -040097 for _, a := range analyzers {
Alan Donovan9f3b32b2018-09-17 09:25:48 -040098 if err := visit(a); err != nil {
99 return err
100 }
101 }
102
Alan Donovan8149dec2018-10-10 14:27:22 -0400103 // 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
Alan Donovan9f3b32b2018-09-17 09:25:48 -0400113 return nil
114}
115
116func 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}
Michaël Lévesque-Dion6422fca2020-08-10 19:12:37 +0000124
125type CycleInRequiresGraphError struct {
126 AnalyzerNames map[string]bool
127}
128
129func (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}