diff --git a/tools/checklocks/analysis.go b/tools/checklocks/analysis.go index e342ac8c6d..49809bc5eb 100644 --- a/tools/checklocks/analysis.go +++ b/tools/checklocks/analysis.go @@ -777,6 +777,11 @@ func (pc *passContext) checkBasicBlock(fn *ssa.Function, block *ssa.BasicBlock, // checkFunction checks a function invocation, typically starting with nil lockState. func (pc *passContext) checkFunction(call callCommon, fn *ssa.Function, lff *lockFunctionFacts, parent *lockState, force bool) { + // Track current function for diagnostics when SSA positions are missing. + prevFn := pc.curFn + pc.curFn = fn + defer func() { pc.curFn = prevFn }() + defer func() { // Mark this function as checked. This is used by the top-level // loop to ensure that all anonymous functions are scanned, if diff --git a/tools/checklocks/annotations.go b/tools/checklocks/annotations.go index 62a4f2e3be..9635711bf0 100644 --- a/tools/checklocks/annotations.go +++ b/tools/checklocks/annotations.go @@ -84,17 +84,41 @@ func (pc *passContext) addForce(pos token.Pos) { // maybeFail checks a potential failure against a specific failure map. func (pc *passContext) maybeFail(pos token.Pos, fmtStr string, args ...any) { - if fd, ok := pc.failures[pc.positionKey(pos)]; ok { + // Build the message string to allow deduplication. + msg := fmt.Sprintf(fmtStr, args...) + + origPos := pos + reportPos := pos + if !pos.IsValid() && pc.curFn != nil { + reportPos = pc.curFn.Pos() + } + + key := pc.positionKey(origPos) + + // Deduplicate: if we already reported this exact message for the same + // position key, ignore subsequent identical diagnostics so they donʼt + // affect failure-count logic. + if seenMsgs, ok := pc.reported[key]; ok { + if _, dup := seenMsgs[msg]; dup { + return + } + } else { + pc.reported[key] = make(map[string]struct{}) + } + + pc.reported[key][msg] = struct{}{} + + if fd, ok := pc.failures[key]; ok { fd.seen++ return } - if _, ok := pc.exemptions[pc.positionKey(pos)]; ok { + if _, ok := pc.exemptions[key]; ok { return // Ignored, not counted. } - if !enableWrappers && !pos.IsValid() { + if !enableWrappers && !origPos.IsValid() { return // Ignored, implicit. } - pc.pass.Reportf(pos, fmtStr, args...) + pc.pass.Reportf(reportPos, fmtStr, args...) } // checkFailure checks for the expected failure counts. diff --git a/tools/checklocks/checklocks.go b/tools/checklocks/checklocks.go index a0866a0711..03c2dce2df 100644 --- a/tools/checklocks/checklocks.go +++ b/tools/checklocks/checklocks.go @@ -73,7 +73,9 @@ type passContext struct { failures map[positionKey]*failData exemptions map[positionKey]struct{} forced map[positionKey]struct{} + reported map[positionKey]map[string]struct{} functions map[*ssa.Function]struct{} + curFn *ssa.Function observations map[types.Object]*objectObservations } @@ -143,6 +145,7 @@ func run(pass *analysis.Pass) (any, error) { exemptions: make(map[positionKey]struct{}), forced: make(map[positionKey]struct{}), functions: make(map[*ssa.Function]struct{}), + reported: make(map[positionKey]map[string]struct{}), } // Find all line failure annotations.