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: 109b3ac598a8594d41450495f56c1d82b84c92a5 GitHub-Pull-Request: golang/tools#598 Reviewed-on: https://go-review.googlesource.com/c/tools/+/710435 Auto-Submit: Alan Donovan <adonovan@google.com> Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Alan Donovan <adonovan@google.com> TryBot-Bypass: Alan Donovan <adonovan@google.com>
diff --git a/cmd/deadcode/deadcode.go b/cmd/deadcode/deadcode.go index 0c0b7ec..d22711e 100644 --- a/cmd/deadcode/deadcode.go +++ b/cmd/deadcode/deadcode.go
@@ -174,9 +174,23 @@ // course address-taken and there exists a dynamic call of // that signature, so when they are unreachable, it is // invariably because the parent is unreachable. - var sourceFuncs []*ssa.Function - generated := make(map[string]bool) + var ( + sourceFuncs []*ssa.Function + generated = make(map[string]bool) + interfaceTypes = make(map[*types.Package][]*types.Interface) + ) packages.Visit(initial, nil, func(p *packages.Package) { + // Collect interfaces by package for marker method identification. + var interfaces []*types.Interface + scope := p.Types.Scope() + for _, name := range scope.Names() { + if typeName, ok := scope.Lookup(name).(*types.TypeName); ok && + types.IsInterface(typeName.Type()) { + interfaces = append(interfaces, typeName.Type().Underlying().(*types.Interface)) + } + } + interfaceTypes[p.Types] = interfaces + for _, file := range p.Syntax { for _, decl := range file.Decls { if decl, ok := decl.(*ast.FuncDecl); ok { @@ -334,10 +348,17 @@ continue } + // Marker methods should not be reported + marker := isMarkerMethod(fn, interfaceTypes[fn.Pkg.Pkg]) + if marker { + continue + } + functions = append(functions, jsonFunction{ Name: prettyName(fn, false), Position: toJSONPosition(posn), Generated: gen, + Marker: marker, }) } if len(functions) > 0 { @@ -538,6 +559,30 @@ } } +// isMarkerMethod reports whether fn is a marker method: +// an unexported, empty-bodied method with no parameters or results +// that implements some named interface type in the same package. +func isMarkerMethod(fn *ssa.Function, interfaceTypes []*types.Interface) bool { + // Is it an unexported method of no params/results? + if !(fn.Signature.Recv() != nil && + !ast.IsExported(fn.Name()) && + fn.Signature.Params() == nil && + fn.Signature.Results() == nil) { + return false + } + + // Does the method have an empty body? + body := fn.Syntax().(*ast.FuncDecl).Body + if body == nil || len(body.List) > 0 { + return false + } + + // Does it implement some named interface type in this package? + return slices.ContainsFunc(interfaceTypes, func(iface *types.Interface) bool { + return types.Implements(fn.Signature.Recv().Type(), iface) + }) +} + // -- output protocol (for JSON or text/template) -- // Keep in sync with doc comment! @@ -546,6 +591,7 @@ Name string // name (sans package qualifier) Position jsonPosition // file/line/column of declaration Generated bool // function is declared in a generated .go file + Marker bool // function is a marker interface method } func (f jsonFunction) String() string { return f.Name }
diff --git a/cmd/deadcode/doc.go b/cmd/deadcode/doc.go index bd47424..aff22c6 100644 --- a/cmd/deadcode/doc.go +++ b/cmd/deadcode/doc.go
@@ -43,6 +43,13 @@ as determined by the special comment described in https://go.dev/s/generatedcode. Use the -generated flag to include them. +The tool also does not report marker interface methods by default. +Marker interface methods are typically used to create compile-time constraints +to ensure that only specific types can implement a particular interface. +These methods have no other functionality (empty function body) and are never invoked. +Although marker interface methods are technically unreachable, removing them would break +the interface implementation. Hence, the tool excludes them from the report. + In any case, just because a function is reported as dead does not mean it is unconditionally safe to delete it. For example, a dead function may be referenced by another dead function, and a dead method may be @@ -121,6 +128,7 @@ Name string // name (sans package qualifier) Position Position // file/line/column of function declaration Generated bool // function is declared in a generated .go file + Marker bool // function is a marker interface method } type Edge struct {
diff --git a/cmd/deadcode/testdata/marker.txtar b/cmd/deadcode/testdata/marker.txtar new file mode 100644 index 0000000..a4ff674 --- /dev/null +++ b/cmd/deadcode/testdata/marker.txtar
@@ -0,0 +1,43 @@ +# Test of marker suppression functionality. +# Deadcode should not identify marker interface methods as unreachable + + deadcode -filter= example.com +!want "T.isMarker" +want "R.isNotMarker" +!want "P.greet" +want "Q.greet" + +-- go.mod -- +module example.com +go 1.18 + +-- main.go -- +package main +import "fmt" + +// Marker method: implements interface, unexported, no params, no results, empty body +type Marker interface { + isMarker() +} +type T struct{} + +func (t *T) isMarker() {} + +// Not marker method: does not implement interface +type R struct {} +func (r *R) isNotMarker() {} + +// Ensure that it still reports valid interface methods +// when unused +type Greeter interface { + greet() +} +type P struct {} +func (p *P) greet() {fmt.Println("P: hello")} +type Q struct {} +func (q *Q) greet() {fmt.Println("Q: hello")} // this method should be reported + +func main () { + var p P + p.greet() +} \ No newline at end of file