checklocks: fix panic on cross-package use of unexported global guards - #14078
Open
tigerquoll wants to merge 1 commit into
Open
checklocks: fix panic on cross-package use of unexported global guards#14078tigerquoll wants to merge 1 commit into
tigerquoll wants to merge 1 commit into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
An annotation whose guard is an unexported package-level variable panicked the analyzer whenever the annotated function was called, or the annotated field accessed, from another package. Export data does not include unexported package-level variables, so the guard package's SSA members do not contain the object and the unchecked type assertion in resolveCommon failed. Resolution now yields an explicitly unavailable resolvedValue in that case, and the guard is skipped at the use site rather than enforced or reported. The annotation itself remains valid, and is still enforced within the package that declares the guard. Exported globals are unaffected. The README documents the limitation, since silently skipping the guard leaves documentation as the channel for guiding annotation authors.
tigerquoll
force-pushed
the
checklocks-global-guard-fix
branch
from
August 12, 2026 05:45
4f07cea to
2d29af2
Compare
This was referenced Aug 12, 2026
tigerquoll
added a commit
to tigerquoll/yunikorn-k8shim
that referenced
this pull request
Aug 12, 2026
The analyser is gVisor's tools/checklocks, which gVisor does not publish as an importable module: the previous pin reached into the gvisor repository and took the whole tree with it. github.com/tigerquoll/checklocks is a standalone extraction of the analyser from gvisor commit 1919d963 with three fixes that these annotations need, the first of them filed upstream as google/gvisor#14078. Its own README calls it a temporary home, so this pin moves again once the fixes land upstream. This unblocks the branch: with the previous pin "make checklocks" panicked on pkg/shim, which calls the annotated NewPlaceholderManager from another package. It now passes. The dispatcher regains the negative preconditions on its self locking package functions. The fix for pointer typed global guards makes the annotation and the acquisition resolve to the same lock, so a caller in this package that holds the dispatcher lock directly is now reported. A caller that reaches the lock through getDispatcher, and any caller outside the package, still cannot be checked; both are recorded at the declaration site. The tools module go directive follows the analyser at 1.25.0, below the shim toolchain, so building the tool can no longer trigger a toolchain switch. The guard on the toolchain stays: what it protects against is the tool and go vet running different toolchains.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Any checklocks annotation whose guard is an unexported package-level variable panics the analyzer when the annotated function is called — or the annotated field is accessed — from a different package:
Both resolution paths are affected —
resolveCall(viacheckFunctionCall) andresolveField(viacheckGuards/checkFieldAccess). Minimal repro matrix (two packages: the guard-defining one and a caller):+checklocksexclude:etc.)+checklocks:)Root cause
resolveCommondoespkg.Members[g.ObjectName].(ssa.Value)unchecked. For a cross-package use site, the guard package's SSA is created from export data, which does not include unexported package-level variables — the member lookup returns nil and the type assertion panics. (The interface-conversion, rather than nil-pointer, panic shows the package itself resolved; only the member is missing.)History: #7721 (2022) fixed the exported cross-package case by adding the
ImportedPackagelookup, but the tests it added only cover exported guards. The field-path variant of this panic has been latent since then; the call-path variant became commonly reachable whenchecklocksexclude{,write}landed (#12439).Fix
Resolution now yields an explicitly unavailable
resolvedValuewhen the guard object has no member in the resolving package, and the five use sites skip such guards instead of enforcing, reporting, or panicking.unavailableis deliberately distinct from the existing invalid state: invalid values are reported as"field %s cannot be resolved"in some paths andpanicinlockState.isHeld/lockFieldin others, and neither is right here — the annotation itself is valid and remains fully enforced inside its defining package, so a use site that cannot materialize the object should be silent.Skipping is sound: a consuming package could only hold the unresolvable guard via an acquire-annotated API whose fact references the same unresolvable object, so the acquisition could never enter its lock state either — the skipped check is vacuously unviolatable. This matches the analyzer's existing elision behavior (newly allocated objects, closures). In
checkGuards, an unavailable guard is also excluded fromguardsFoundso it cannot silently relax a mixed+checkatomicfield.The README now documents the limitation in both places that list global locks as resolvable: an unexported global lock is enforceable only within its declaring package; export the lock if cross-package enforcement is required.
Tests
test/crosspkg+test/globals.gogain unexported-global-guard coverage for both paths, with+checklocksfailexpectations verified load-bearing (removing them makes the analyzer reportmissing expected failure). Before the fix, the new tests reproduce the panic; after it, the analyzer's output over the whole test tree is otherwise identical to the pre-change baseline, and the exported cross-package cases still enforce.Generated by the Author with assistance from Claude Code