@@ -221,7 +221,7 @@ func (pc *passContext) checkGuards(inst almostInst, from ssa.Value, accessObj ty
221
221
)
222
222
223
223
// Load the facts for the object accessed.
224
- pc .pass . ImportObjectFact (accessObj , & lgf )
224
+ pc .importLockGuardFacts (accessObj , from . Type () , & lgf )
225
225
226
226
// Check guards held.
227
227
for guardName , fgr := range lgf .GuardedBy {
@@ -495,39 +495,46 @@ func (pc *passContext) checkFunctionCall(call callCommon, fn *types.Func, lff *l
495
495
// Update all lock state accordingly.
496
496
pc .postFunctionCallUpdate (call , lff , ls , false /* aliases */ )
497
497
498
+ // Fast-path handling for sync.(RW)Mutex/Locker helper methods.
499
+ if fn == nil || fn .Pkg () == nil || len (args ) == 0 {
500
+ return
501
+ }
502
+
503
+ if ! (lockerRE .MatchString (fn .FullName ()) || mutexRE .MatchString (fn .FullName ())) {
504
+ return
505
+ }
506
+
498
507
// Check if it's a method dispatch for something in the sync package.
499
508
// See: https://godoc.org/golang.org/x/tools/go/ssa#Function
500
509
501
- if (lockerRE .MatchString (fn .FullName ()) || mutexRE .MatchString (fn .FullName ())) && len (args ) > 0 {
502
- rv := makeResolvedValue (args [0 ], nil )
503
- isExclusive := false
504
- switch fn .Name () {
505
- case "Lock" , "NestedLock" :
506
- isExclusive = true
507
- fallthrough
508
- case "RLock" :
509
- if s , ok := ls .lockField (rv , isExclusive ); ! ok && ! lff .Ignore {
510
- if _ , ok := pc .forced [pc .positionKey (call .Pos ())]; ! ok {
511
- // Double locking a mutex that is already locked.
512
- pc .maybeFail (call .Pos (), "%s already locked (locks: %s)" , s , ls .String ())
513
- }
510
+ rv := makeResolvedValue (args [0 ], nil )
511
+ isExclusive := false
512
+ switch fn .Name () {
513
+ case "Lock" , "NestedLock" :
514
+ isExclusive = true
515
+ fallthrough
516
+ case "RLock" :
517
+ if s , ok := ls .lockField (rv , isExclusive ); ! ok && ! lff .Ignore {
518
+ if _ , ok := pc .forced [pc .positionKey (call .Pos ())]; ! ok {
519
+ // Double locking a mutex that is already locked.
520
+ pc .maybeFail (call .Pos (), "%s already locked (locks: %s)" , s , ls .String ())
514
521
}
515
- case "Unlock" , "NestedUnlock" :
516
- isExclusive = true
517
- fallthrough
518
- case "RUnlock" :
519
- if s , ok := ls . unlockField ( rv , isExclusive ); ! ok && ! lff . Ignore {
520
- if _ , ok := pc . forced [ pc . positionKey ( call . Pos ())] ; ! ok {
521
- // Unlocking something that is already unlocked.
522
- pc . maybeFail ( call . Pos (), "%s already unlocked or locked differently (locks: %s)" , s , ls . String ())
523
- }
522
+ }
523
+ case "Unlock" , "NestedUnlock" :
524
+ isExclusive = true
525
+ fallthrough
526
+ case "RUnlock" :
527
+ if s , ok := ls . unlockField ( rv , isExclusive ) ; ! ok && ! lff . Ignore {
528
+ if _ , ok := pc . forced [ pc . positionKey ( call . Pos ())]; ! ok {
529
+ // Unlocking something that is already unlocked.
530
+ pc . maybeFail ( call . Pos (), "%s already unlocked or locked differently (locks: %s)" , s , ls . String ())
524
531
}
525
- case "DowngradeLock" :
526
- if s , ok := ls . downgradeField ( rv ); ! ok {
527
- if _ , ok := pc . forced [ pc . positionKey ( call . Pos ())] ; ! ok && ! lff . Ignore {
528
- // Downgrading something that may not be downgraded.
529
- pc . maybeFail ( call . Pos (), "%s already unlocked or not exclusive (locks: %s)" , s , ls . String ())
530
- }
532
+ }
533
+ case "DowngradeLock" :
534
+ if s , ok := ls . downgradeField ( rv ) ; ! ok {
535
+ if _ , ok := pc . forced [ pc . positionKey ( call . Pos ())]; ! ok && ! lff . Ignore {
536
+ // Downgrading something that may not be downgraded.
537
+ pc . maybeFail ( call . Pos (), "%s already unlocked or not exclusive (locks: %s)" , s , ls . String ())
531
538
}
532
539
}
533
540
}
@@ -672,14 +679,11 @@ func (pc *passContext) checkInstruction(inst ssa.Instruction, lff *lockFunctionF
672
679
return nil , nil
673
680
}
674
681
}
675
- // Analyze the closure without bindings. This means that we
676
- // assume no lock facts or have any existing lock state. Only
677
- // trivial closures are acceptable in this case.
678
- clfn := x .Fn .(* ssa.Function )
679
- nlff := lockFunctionFacts {
680
- Ignore : lff .Ignore , // Inherit ignore.
681
- }
682
- pc .checkFunction (nil , clfn , & nlff , nil , false /* force */ )
682
+ // Perform a fresh analysis of the closure body without propagating
683
+ // any lock state from the point of construction. This conservatively
684
+ // validates the closure in isolation while still inheriting the
685
+ // caller-supplied lock facts via checkClosure.
686
+ pc .checkClosure (nil , x , lff , ls )
683
687
case * ssa.Return :
684
688
return x , ls // Valid return state.
685
689
}
@@ -843,11 +847,47 @@ func (pc *passContext) checkFunction(call callCommon, fn *ssa.Function, lff *loc
843
847
}
844
848
}
845
849
850
+ // isMutexType checks if a given type is sync.Mutex or sync.RWMutex.
851
+ // Simplified implementation focusing on direct Named type check.
852
+ func (pc * passContext ) isMutexType (typ types.Type ) bool {
853
+ // Fast-path: unwrap aliases and pointers.
854
+ seen := make (map [types.Type ]struct {})
855
+ cur := typ
856
+ for {
857
+ if _ , dup := seen [cur ]; dup {
858
+ // Cycle guard, should never happen.
859
+ break
860
+ }
861
+ seen [cur ] = struct {}{}
862
+
863
+ cur = types .Unalias (cur )
864
+
865
+ switch t := cur .(type ) {
866
+ case * types.Pointer :
867
+ cur = t .Elem ()
868
+ continue
869
+ case * types.Named :
870
+ obj := t .Obj ()
871
+ if obj != nil && obj .Pkg () != nil {
872
+ if obj .Pkg ().Path () == "sync" && (obj .Name () == "Mutex" || obj .Name () == "RWMutex" ) {
873
+ return true
874
+ }
875
+ }
876
+ case * types.TypeParam :
877
+ // A generic type parameter can be any type; conservatively treat as non-mutex.
878
+ return false
879
+ }
880
+ // No further unwrapping possible.
881
+ break
882
+ }
883
+ return false
884
+ }
885
+
846
886
// checkInferred checks for any inferred lock annotations.
847
887
func (pc * passContext ) checkInferred () {
848
888
for obj , oo := range pc .observations {
849
889
var lgf lockGuardFacts
850
- pc .pass . ImportObjectFact (obj , & lgf )
890
+ pc .importLockGuardFacts (obj , obj . Type () , & lgf )
851
891
for other , count := range oo .counts {
852
892
// Is this already a guard?
853
893
if _ , ok := lgf .GuardedBy [other .Name ()]; ok {
@@ -858,8 +898,69 @@ func (pc *passContext) checkInferred() {
858
898
// hint that this may something you wish to annotate.
859
899
const threshold = 0.9
860
900
if usage := float64 (count ) / float64 (oo .total ); usage >= threshold {
901
+ // Don't suggest annotations for fields that are themselves mutexes.
902
+ if pc .isMutexType (obj .Type ()) {
903
+ continue
904
+ }
905
+
861
906
pc .maybeFail (obj .Pos (), "may require checklocks annotation for %s, used with lock held %2.0f%% of the time" , other .Name (), usage * 100 )
862
907
}
863
908
}
864
909
}
865
910
}
911
+
912
+ // importLockGuardFacts imports lock guard facts for the given object and type.
913
+ func (pc * passContext ) importLockGuardFacts (obj types.Object , typ types.Type , lgf * lockGuardFacts ) {
914
+ // Load the facts for the object accessed.
915
+ pc .pass .ImportObjectFact (obj , lgf )
916
+
917
+ // For instantiated generic types, the field object differs from its origin
918
+ // declaration object where facts are attached during export. If we did not
919
+ // find any facts on the instantiated object, retry with the origin object.
920
+ if len (lgf .GuardedBy ) == 0 && lgf .AtomicDisposition == 0 {
921
+ if h , ok := obj .(interface { Origin () types.Object }); ok {
922
+ if o := h .Origin (); o != nil && o != obj {
923
+ pc .pass .ImportObjectFact (o , lgf )
924
+ // If we successfully retrieved facts from the origin
925
+ // object, persist them on the instantiated field object
926
+ // so that later phases (e.g. inference) can retrieve
927
+ // them directly via ImportObjectFact.
928
+ if len (lgf .GuardedBy ) > 0 || lgf .AtomicDisposition != 0 {
929
+ pc .pass .ExportObjectFact (obj , lgf )
930
+ }
931
+ }
932
+ }
933
+ // Additional fallback: for instantiated generic structs, facts are
934
+ // attached to the original field object inside the generic definition,
935
+ // which belongs to the *origin* Named type. Locate that origin field
936
+ // (matched by name) and import its facts.
937
+ if len (lgf .GuardedBy ) == 0 && lgf .AtomicDisposition == 0 {
938
+ // Determine the originating Named type of the parent struct.
939
+ baseTyp := typ
940
+ for {
941
+ baseTyp = types .Unalias (baseTyp )
942
+ if ptr , ok := baseTyp .(* types.Pointer ); ok {
943
+ baseTyp = ptr .Elem ()
944
+ continue
945
+ }
946
+ break
947
+ }
948
+ if named , ok := baseTyp .(* types.Named ); ok {
949
+ if origin := named .Origin (); origin != nil && origin != named {
950
+ if structOrigin , ok := origin .Underlying ().(* types.Struct ); ok {
951
+ for i := 0 ; i < structOrigin .NumFields (); i ++ {
952
+ fld := structOrigin .Field (i )
953
+ if fld .Name () == obj .Name () {
954
+ pc .pass .ImportObjectFact (fld , lgf )
955
+ if len (lgf .GuardedBy ) > 0 || lgf .AtomicDisposition != 0 {
956
+ pc .pass .ExportObjectFact (obj , lgf )
957
+ }
958
+ break
959
+ }
960
+ }
961
+ }
962
+ }
963
+ }
964
+ }
965
+ }
966
+ }
0 commit comments