Skip to content

fix(tools/checklocks): Add generics support #11740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

kakkoyun
Copy link

@kakkoyun kakkoyun commented May 19, 2025

Summary

This PR introduces support for Go generics to the tools/checklocks static analysis tool. It resolves issues where checklocks would produce false-positive warnings ("may require checklocks annotation") for mutex fields within generic types and ensures that +checklocksfail annotations behave correctly with generic code.

The core of the changes involves updating the type resolution and fact-finding logic within analysis.go and facts.go to correctly interpret lock annotations in the context of generic structs and their instantiations.

Key Changes

  • Generic Type Handling: Modified analysis.go to correctly resolve types and associate lock annotations for fields within generic structs. This includes improvements to how guarded-by facts are discovered and applied, particularly using Origin() to trace back to generic definitions.
  • Mutex Identification: Refined isMutexType to accurately identify mutex types, including those used within generic contexts.
  • SSA Integration: Ensured proper handling of *ssa.Alloc and *ssa.Store operations in state.go and analysis.go respectively, which was crucial for resolving a regression related to lock state propagation in generic helper functions.
  • New Test Cases: Added tools/checklocks/test/generics.go with specific test cases to validate checklocks behavior with generic resources, including scenarios with +checklocks and +checklocksfail annotations.
  • Code Refinement: Refactored parts of analysis.go to use a canonical helper function (importLockGuardFacts) for importing lock guard facts, reducing code duplication. Removed extensive debug logging now that the core functionality is stable.

Impact & Testing

  • With these changes, checklocks no longer produces false-positive "may require checklocks annotation" warnings for mutex fields in generic types.
  • The +checklocksfail annotation now correctly identifies (or not identify) violations in test cases involving generics.
  • All new and existing tests related to generic types in tools/checklocks/test/generics.go now pass as expected.
  • Known baseline failures in test/closures.go and test/defer.go (which predate this work and exist in the main branch) are unaffected and considered out of scope for this PR.
# gvisor.dev/gvisor/tools/checklocks/test
-: return with unexpected locks held (locks: &({param:tc}.mu) exclusively)
-: return with unexpected locks held (locks: &({param:tc}.mu) exclusively)
-: return with unexpected locks held (locks: &({param:tc}.mu) exclusively)
-: return with unexpected locks held (locks: &({param:tc}.mu) exclusively)
-: return with unexpected locks held (locks: &({param:tc}.mu) exclusively)
-: return with unexpected locks held (locks: &({param:tc}.mu) exclusively)
-: lock a.mu (&({param:a}.mu)) not held exclusively (locks: no locks held)

The missing file and line numbers are handled in #11741.

Fixes #11671

Signed-off-by: Kemal Akkoyun [email protected]

@kakkoyun
Copy link
Author

cc @EtiennePerot

This commit introduces support for Go generics in the checklocks analyzer,
addressing issues with false-positive warnings and ensuring correct
behavior with generic types.

Key changes include:
- Updated lock guard fact handling in analysis.go for generic types.
- Added test cases for generics in tools/checklocks/test/generics.go.
- Resolved regressions specific to generic value guards.

All tests related to generics now pass. Pre-existing baseline failures
in closure and defer handling are out of scope for this change.

Fixes google#11671

Signed-off-by: Kemal Akkoyun <[email protected]>
@kakkoyun kakkoyun force-pushed the fix_generics_draft branch from e2bec2b to a195741 Compare May 19, 2025 13:54
Copy link
Contributor

@EtiennePerot EtiennePerot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on!

// This statement should be unreachable, but the Go compiler cannot prove
// that the above type switch returns in all cases (due to `break` in some
// branches). Provide a safe default to satisfy the type checker.
return fmt.Sprintf("{unknown:%T:%p}", v, v), nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please panic instead, since this should be unreachable.

// itself.
func genericInstantiate() {
var _ genericGuardStruct[int]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would happen in the case where a library defines the genericGuardStruct without ever instantiating it, as all instantiations of genericGuardStruct would be in other libraries that import it? I think this would be a more common case than what this test file is doing.

@@ -495,39 +495,46 @@ func (pc *passContext) checkFunctionCall(call callCommon, fn *types.Func, lff *l
// Update all lock state accordingly.
pc.postFunctionCallUpdate(call, lff, ls, false /* aliases */)

// Fast-path handling for sync.(RW)Mutex/Locker helper methods.
if fn == nil || fn.Pkg() == nil || len(args) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand how this works, but this feels brittle. What is this skipping over?

case *types.Named:
obj := t.Obj()
if obj != nil && obj.Pkg() != nil {
if obj.Pkg().Path() == "sync" && (obj.Name() == "Mutex" || obj.Name() == "RWMutex") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this work for the gVisor codebase, which uses import path gvisor.dev/gvisor/pkg/sync?
https://github.com/search?q=repo%3Agoogle%2Fgvisor+%22%5C%22gvisor.dev%2Fgvisor%2Fpkg%2Fsync%5C%22%22&type=code

cur := typ
for {
if _, dup := seen[cur]; dup {
// Cycle guard, should never happen.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Panic?

@kakkoyun kakkoyun marked this pull request as draft May 20, 2025 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

tools/checklocks: Generics support
2 participants