6
6
"go/types"
7
7
"strconv"
8
8
"strings"
9
- "sync"
10
9
11
10
"github.com/gostaticanalysis/analysisutil"
12
11
"golang.org/x/tools/go/analysis"
@@ -23,6 +22,7 @@ func NewAnalyzer() *analysis.Analyzer {
23
22
Requires : []* analysis.Analyzer {
24
23
buildssa .Analyzer ,
25
24
},
25
+ FactTypes : []analysis.Fact {(* ctxFact )(nil )},
26
26
}
27
27
}
28
28
@@ -39,27 +39,38 @@ const (
39
39
CtxInOut = CtxIn | CtxOut
40
40
)
41
41
42
- var (
43
- checkedMap = make (map [string ]bool )
44
- checkedMapLock sync.RWMutex
45
- c * collector
46
- )
42
+ type resInfo struct {
43
+ Valid bool
44
+ Funcs []string
45
+ }
46
+
47
+ type ctxFact map [string ]resInfo
48
+
49
+ func (* ctxFact ) String () string { return "ctxCheck" }
50
+ func (* ctxFact ) AFact () {}
47
51
48
52
type runner struct {
49
53
pass * analysis.Pass
50
54
ctxTyp * types.Named
51
55
ctxPTyp * types.Pointer
52
56
cmpPath string
53
57
skipFile map [* ast.File ]bool
58
+
59
+ currentFact ctxFact
54
60
}
55
61
56
62
func NewRun (pkgs []* packages.Package ) func (pass * analysis.Pass ) (interface {}, error ) {
57
- c = newCollector (pkgs )
63
+ m := make (map [string ]bool )
64
+ for _ , pkg := range pkgs {
65
+ m [strings .Split (pkg .PkgPath , "/" )[0 ]] = true
66
+ }
58
67
return func (pass * analysis.Pass ) (interface {}, error ) {
59
- defer c .DecUse (pass )
68
+ // skip different repo
69
+ if ! m [strings .Split (pass .Pkg .Path (), "/" )[0 ]] {
70
+ return nil , nil
71
+ }
60
72
61
- r := new (runner )
62
- r .run (pass )
73
+ new (runner ).run (pass )
63
74
return nil , nil
64
75
}
65
76
}
@@ -90,21 +101,33 @@ func (r *runner) run(pass *analysis.Pass) {
90
101
}
91
102
92
103
r .skipFile = make (map [* ast.File ]bool )
104
+ r .currentFact = make (ctxFact )
93
105
106
+ var tmpFuncs []* ssa.Function
94
107
for _ , f := range funcs {
95
108
// skip checked function
96
109
key := f .RelString (nil )
97
- _ , ok := getValue (key )
98
- if ok {
110
+ if _ , ok := r .currentFact [key ]; ok {
99
111
continue
100
112
}
101
113
102
114
if ! r .checkIsEntry (f , f .Pos ()) {
115
+ // record the result of nomal function
116
+ checkingMap := make (map [string ]bool )
117
+ checkingMap [key ] = true
118
+ r .setFact (key , r .checkFuncWithoutCtx (f , checkingMap ), f .Name ())
103
119
continue
104
120
}
105
121
122
+ tmpFuncs = append (tmpFuncs , f )
123
+ }
124
+
125
+ for _ , f := range tmpFuncs {
106
126
r .checkFuncWithCtx (f )
107
- setValue (key , true )
127
+ }
128
+
129
+ if len (r .currentFact ) > 0 {
130
+ pass .ExportPackageFact (& r .currentFact )
108
131
}
109
132
}
110
133
@@ -269,16 +292,6 @@ func (r *runner) collectCtxRef(f *ssa.Function) (refMap map[ssa.Instruction]bool
269
292
return
270
293
}
271
294
272
- func (r * runner ) buildPkg (f * ssa.Function ) (ff * ssa.Function ) {
273
- if f .Blocks != nil {
274
- ff = f
275
- return
276
- }
277
-
278
- ff = c .GetFunction (f )
279
- return
280
- }
281
-
282
295
func (r * runner ) checkIsSameRepo (s string ) bool {
283
296
return strings .HasPrefix (s , r .cmpPath + "/" )
284
297
}
@@ -313,31 +326,10 @@ func (r *runner) checkFuncWithCtx(f *ssa.Function) {
313
326
}
314
327
315
328
key := ff .RelString (nil )
316
- valid , ok := getValue (key )
329
+ res , ok := r . getValue (key , ff )
317
330
if ok {
318
- if ! valid {
319
- r .pass .Reportf (instr .Pos (), "Function `%s` should pass the context parameter" , ff .Name ())
320
- }
321
- continue
322
- }
323
-
324
- // check is thunk or bound
325
- if strings .HasSuffix (key , "$thunk" ) || strings .HasSuffix (key , "$bound" ) {
326
- continue
327
- }
328
-
329
- // if ff has no ctx, start deep traversal check
330
- if ! r .checkIsEntry (ff , instr .Pos ()) {
331
- if ff = r .buildPkg (ff ); ff == nil {
332
- continue
333
- }
334
-
335
- checkingMap := make (map [string ]bool )
336
- checkingMap [key ] = true
337
- valid := r .checkFuncWithoutCtx (ff , checkingMap )
338
- setValue (key , valid )
339
- if ! valid {
340
- r .pass .Reportf (instr .Pos (), "Function `%s` should pass the context parameter" , ff .Name ())
331
+ if ! res .Valid {
332
+ r .pass .Reportf (instr .Pos (), "Function `%s` should pass the context parameter" , strings .Join (reverse (res .Funcs ), "->" ))
341
333
}
342
334
}
343
335
}
@@ -346,6 +338,7 @@ func (r *runner) checkFuncWithCtx(f *ssa.Function) {
346
338
347
339
func (r * runner ) checkFuncWithoutCtx (f * ssa.Function , checkingMap map [string ]bool ) (ret bool ) {
348
340
ret = true
341
+ orgKey := f .RelString (nil )
349
342
for _ , b := range f .Blocks {
350
343
for _ , instr := range b .Instrs {
351
344
tp , ok := r .getCtxType (instr )
@@ -362,7 +355,6 @@ func (r *runner) checkFuncWithoutCtx(f *ssa.Function, checkingMap map[string]boo
362
355
if tp & CtxInField == 0 {
363
356
ret = false
364
357
}
365
- continue
366
358
}
367
359
368
360
ff := r .getFunction (instr )
@@ -371,11 +363,13 @@ func (r *runner) checkFuncWithoutCtx(f *ssa.Function, checkingMap map[string]boo
371
363
}
372
364
373
365
key := ff .RelString (nil )
374
- valid , ok := getValue (key )
366
+ res , ok := r . getValue (key , ff )
375
367
if ok {
376
- if ! valid {
368
+ if ! res . Valid {
377
369
ret = false
378
- r .pass .Reportf (instr .Pos (), "Function `%s` should pass the context parameter" , ff .Name ())
370
+
371
+ // save the call link
372
+ r .setFact (orgKey , res .Valid , res .Funcs ... )
379
373
}
380
374
continue
381
375
}
@@ -386,21 +380,21 @@ func (r *runner) checkFuncWithoutCtx(f *ssa.Function, checkingMap map[string]boo
386
380
}
387
381
388
382
if ! r .checkIsEntry (ff , instr .Pos ()) {
389
- // handler ring call
390
- if checkingMap [ key ] {
383
+ // cannot get info from fact, skip
384
+ if ff . Blocks == nil {
391
385
continue
392
386
}
393
- checkingMap [key ] = true
394
387
395
- if ff = r .buildPkg (ff ); ff == nil {
388
+ // handler ring call
389
+ if checkingMap [key ] {
396
390
continue
397
391
}
392
+ checkingMap [key ] = true
398
393
399
394
valid := r .checkFuncWithoutCtx (ff , checkingMap )
400
- setValue ( key , valid )
395
+ r . setFact ( orgKey , valid , ff . Name () )
401
396
if ! valid {
402
397
ret = false
403
- r .pass .Reportf (instr .Pos (), "Function `%s` should pass the context parameter" , ff .Name ())
404
398
}
405
399
}
406
400
}
@@ -501,15 +495,39 @@ func (r *runner) isCtxType(tp types.Type) bool {
501
495
return types .Identical (tp , r .ctxTyp ) || types .Identical (tp , r .ctxPTyp )
502
496
}
503
497
504
- func getValue (key string ) (valid , ok bool ) {
505
- checkedMapLock .RLock ()
506
- valid , ok = checkedMap [key ]
507
- checkedMapLock .RUnlock ()
498
+ func (r * runner ) getValue (key string , f * ssa.Function ) (res resInfo , ok bool ) {
499
+ res , ok = r .currentFact [key ]
500
+ if ok {
501
+ return
502
+ }
503
+
504
+ if f .Pkg == nil {
505
+ return
506
+ }
507
+
508
+ var fact ctxFact
509
+ if r .pass .ImportPackageFact (f .Pkg .Pkg , & fact ) {
510
+ res , ok = fact [key ]
511
+ }
508
512
return
509
513
}
510
514
511
- func setValue (key string , valid bool ) {
512
- checkedMapLock .Lock ()
513
- checkedMap [key ] = valid
514
- checkedMapLock .Unlock ()
515
+ func (r * runner ) setFact (key string , valid bool , funcs ... string ) {
516
+ r .currentFact [key ] = resInfo {
517
+ Valid : valid ,
518
+ Funcs : append (r .currentFact [key ].Funcs , funcs ... ),
519
+ }
520
+ }
521
+
522
+ func reverse (arr1 []string ) (arr2 []string ) {
523
+ l := len (arr1 )
524
+ if l == 0 {
525
+ return
526
+ }
527
+ arr2 = make ([]string , l )
528
+ for i := 0 ; i <= l / 2 ; i ++ {
529
+ arr2 [i ] = arr1 [l - 1 - i ]
530
+ arr2 [l - 1 - i ] = arr1 [i ]
531
+ }
532
+ return
515
533
}
0 commit comments