Skip to content

Commit b89c4fe

Browse files
FolkLoreeegopherbot
authored andcommitted
cmd/deadcode: suppress marker interface method reporting
The deadcode tool now suppresses marker interface methods from the default output, as these are typically intentional interface implementations rather than dead code. Marker interface methods are unexported methods that: implement a top-level interface and have no parameters, results, and function body. Add Marker field to jsonFunction struct to indicate when a function is a marker interface method, allowing users to filter or identify these methods in custom format templates. Fixes golang/go#75628 Change-Id: I8271fad800717a96ead18ba9b0f3c4e7b23f864a GitHub-Last-Rev: 109b3ac GitHub-Pull-Request: #598 Reviewed-on: https://go-review.googlesource.com/c/tools/+/710435 Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: David Chase <[email protected]> Reviewed-by: Alan Donovan <[email protected]> TryBot-Bypass: Alan Donovan <[email protected]>
1 parent c2e7979 commit b89c4fe

File tree

3 files changed

+99
-2
lines changed

3 files changed

+99
-2
lines changed

cmd/deadcode/deadcode.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,23 @@ func main() {
174174
// course address-taken and there exists a dynamic call of
175175
// that signature, so when they are unreachable, it is
176176
// invariably because the parent is unreachable.
177-
var sourceFuncs []*ssa.Function
178-
generated := make(map[string]bool)
177+
var (
178+
sourceFuncs []*ssa.Function
179+
generated = make(map[string]bool)
180+
interfaceTypes = make(map[*types.Package][]*types.Interface)
181+
)
179182
packages.Visit(initial, nil, func(p *packages.Package) {
183+
// Collect interfaces by package for marker method identification.
184+
var interfaces []*types.Interface
185+
scope := p.Types.Scope()
186+
for _, name := range scope.Names() {
187+
if typeName, ok := scope.Lookup(name).(*types.TypeName); ok &&
188+
types.IsInterface(typeName.Type()) {
189+
interfaces = append(interfaces, typeName.Type().Underlying().(*types.Interface))
190+
}
191+
}
192+
interfaceTypes[p.Types] = interfaces
193+
180194
for _, file := range p.Syntax {
181195
for _, decl := range file.Decls {
182196
if decl, ok := decl.(*ast.FuncDecl); ok {
@@ -334,10 +348,17 @@ func main() {
334348
continue
335349
}
336350

351+
// Marker methods should not be reported
352+
marker := isMarkerMethod(fn, interfaceTypes[fn.Pkg.Pkg])
353+
if marker {
354+
continue
355+
}
356+
337357
functions = append(functions, jsonFunction{
338358
Name: prettyName(fn, false),
339359
Position: toJSONPosition(posn),
340360
Generated: gen,
361+
Marker: marker,
341362
})
342363
}
343364
if len(functions) > 0 {
@@ -538,6 +559,30 @@ func cond[T any](cond bool, t, f T) T {
538559
}
539560
}
540561

562+
// isMarkerMethod reports whether fn is a marker method:
563+
// an unexported, empty-bodied method with no parameters or results
564+
// that implements some named interface type in the same package.
565+
func isMarkerMethod(fn *ssa.Function, interfaceTypes []*types.Interface) bool {
566+
// Is it an unexported method of no params/results?
567+
if !(fn.Signature.Recv() != nil &&
568+
!ast.IsExported(fn.Name()) &&
569+
fn.Signature.Params() == nil &&
570+
fn.Signature.Results() == nil) {
571+
return false
572+
}
573+
574+
// Does the method have an empty body?
575+
body := fn.Syntax().(*ast.FuncDecl).Body
576+
if body == nil || len(body.List) > 0 {
577+
return false
578+
}
579+
580+
// Does it implement some named interface type in this package?
581+
return slices.ContainsFunc(interfaceTypes, func(iface *types.Interface) bool {
582+
return types.Implements(fn.Signature.Recv().Type(), iface)
583+
})
584+
}
585+
541586
// -- output protocol (for JSON or text/template) --
542587

543588
// Keep in sync with doc comment!
@@ -546,6 +591,7 @@ type jsonFunction struct {
546591
Name string // name (sans package qualifier)
547592
Position jsonPosition // file/line/column of declaration
548593
Generated bool // function is declared in a generated .go file
594+
Marker bool // function is a marker interface method
549595
}
550596

551597
func (f jsonFunction) String() string { return f.Name }

cmd/deadcode/doc.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ By default, the tool does not report dead functions in generated files,
4343
as determined by the special comment described in
4444
https://go.dev/s/generatedcode. Use the -generated flag to include them.
4545
46+
The tool also does not report marker interface methods by default.
47+
Marker interface methods are typically used to create compile-time constraints
48+
to ensure that only specific types can implement a particular interface.
49+
These methods have no other functionality (empty function body) and are never invoked.
50+
Although marker interface methods are technically unreachable, removing them would break
51+
the interface implementation. Hence, the tool excludes them from the report.
52+
4653
In any case, just because a function is reported as dead does not mean
4754
it is unconditionally safe to delete it. For example, a dead function
4855
may be referenced by another dead function, and a dead method may be
@@ -121,6 +128,7 @@ is static or dynamic, and its source line number. For example:
121128
Name string // name (sans package qualifier)
122129
Position Position // file/line/column of function declaration
123130
Generated bool // function is declared in a generated .go file
131+
Marker bool // function is a marker interface method
124132
}
125133
126134
type Edge struct {

cmd/deadcode/testdata/marker.txtar

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Test of marker suppression functionality.
2+
# Deadcode should not identify marker interface methods as unreachable
3+
4+
deadcode -filter= example.com
5+
!want "T.isMarker"
6+
want "R.isNotMarker"
7+
!want "P.greet"
8+
want "Q.greet"
9+
10+
-- go.mod --
11+
module example.com
12+
go 1.18
13+
14+
-- main.go --
15+
package main
16+
import "fmt"
17+
18+
// Marker method: implements interface, unexported, no params, no results, empty body
19+
type Marker interface {
20+
isMarker()
21+
}
22+
type T struct{}
23+
24+
func (t *T) isMarker() {}
25+
26+
// Not marker method: does not implement interface
27+
type R struct {}
28+
func (r *R) isNotMarker() {}
29+
30+
// Ensure that it still reports valid interface methods
31+
// when unused
32+
type Greeter interface {
33+
greet()
34+
}
35+
type P struct {}
36+
func (p *P) greet() {fmt.Println("P: hello")}
37+
type Q struct {}
38+
func (q *Q) greet() {fmt.Println("Q: hello")} // this method should be reported
39+
40+
func main () {
41+
var p P
42+
p.greet()
43+
}

0 commit comments

Comments
 (0)