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