gopls/internal/cache/methodsets: fix panic on types with generic methods
CL 776140 started skipping generic methods when building the method-set
index, as they do not participate in interface satisfaction. But
methodSetInfo allocated the method slice at the full method-set length
and assigned each entry by index, so a skipped method left a nil
*gobMethod hole in the slice.
A type that has a generic method alongside an ordinary one is still
recorded in the index, because the ordinary method gives it a non-zero
mask. The nil hole therefore survives into the serialized index, and an
Implementation (or References) query that later iterates the type's
methods in implements dereferences the nil pointer and crashes gopls.
Build the slice with append, preallocating capacity but not length, so
that skipped methods leave no holes.
Also add a cross-package implementation marker test over a type with
both a generic and an ordinary method, which panics without this fix.
Updates golang/go#77549
diff --git a/gopls/internal/cache/methodsets/methodsets.go b/gopls/internal/cache/methodsets/methodsets.go
index 8be9c9d..5936bd1 100644
--- a/gopls/internal/cache/methodsets/methodsets.go
+++ b/gopls/internal/cache/methodsets/methodsets.go
@@ -392,7 +392,9 @@
var mask uint64
tricky := false
var buf []byte
- methods := make([]*gobMethod, mset.Len())
+ // Preallocate capacity, but grow by append so that skipped
+ // (generic) methods leave no nil holes in the slice.
+ methods := make([]*gobMethod, 0, mset.Len())
for i := 0; i < mset.Len(); i++ {
m := mset.At(i).Obj().(*types.Func)
// Generic methods do not participate in interface satisfaction.
@@ -406,9 +408,10 @@
}
buf = append(append(buf[:0], id...), fp...)
sum := crc32.ChecksumIEEE(buf)
- methods[i] = &gobMethod{ID: id, Fingerprint: fp, Sum: sum, Tricky: isTricky}
+ gm := &gobMethod{ID: id, Fingerprint: fp, Sum: sum, Tricky: isTricky}
+ methods = append(methods, gm)
if setIndexInfo != nil {
- setIndexInfo(methods[i], m) // set Position, PkgPath, ObjectPath
+ setIndexInfo(gm, m) // set Position, PkgPath, ObjectPath
}
mask |= 1 << uint64(((sum>>24)^(sum>>16)^(sum>>8)^sum)&0x3f)
}
diff --git a/gopls/internal/test/marker/testdata/implementation/genericmethods-mixed.txt b/gopls/internal/test/marker/testdata/implementation/genericmethods-mixed.txt
new file mode 100644
index 0000000..e1639d6
--- /dev/null
+++ b/gopls/internal/test/marker/testdata/implementation/genericmethods-mixed.txt
@@ -0,0 +1,42 @@
+Regression test for a crash in the method-set index when a type has
+both an ordinary method and a generic method (Go 1.27).
+
+Generic methods are skipped while building the method-set index (they
+do not participate in interface satisfaction), but the surrounding type
+is still indexed because its ordinary method gives it a non-zero mask.
+A bug once pre-sized the method slice to the full method-set length and
+filled it by index, so skipping the generic method left a nil hole;
+iterating those methods during a search panicked with a nil-pointer
+dereference.
+
+The query and its implementer are deliberately in different packages so
+that the search goes through the serialized methodsets index (the path
+that held the nil hole), not the go/types-based local path.
+
+See the fix in gopls/internal/cache/methodsets, follow-up to golang/go#77549.
+
+-- flags --
+-min_go=go1.27
+
+-- go.mod --
+module example.com
+go 1.27
+
+-- a/a.go --
+package a
+
+// C has an ordinary method G, so it is recorded in the method-set
+// index, and a generic method F, which is skipped during indexing and
+// must not leave a nil hole. C implements b.I via G alone.
+type C struct{} //@loc(C, "C"), implementation("C", I)
+
+func (C) G(int) {}
+
+func (C) F[T any](T) {}
+
+-- b/b.go --
+package b
+
+type I interface { //@loc(I, "I"), implementation("I", C)
+ G(int)
+}
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Hold | +1 |
Thanks for tackling this. However, I don't think this is the right fix. Generic methods are not in the method set of a type; therefore the appropriate fix is in types.MethodSet.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Thanks for tackling this. However, I don't think this is the right fix. Generic methods are not in the method set of a type; therefore the appropriate fix is in types.MethodSet.
Perhaps this is due to the influence of https://go-review.googlesource.com/c/go/+/785740 revert?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Koichi Shiraishi (zchee)Thanks for tackling this. However, I don't think this is the right fix. Generic methods are not in the method set of a type; therefore the appropriate fix is in types.MethodSet.
Perhaps this is due to the influence of https://go-review.googlesource.com/c/go/+/785740 revert?
Yes, we need to un-revert that change, but it was (I think) unfairly blamed for a bug in the api checker, which assumes that generic methods can be found in types.MethodSet; it needs to be fixed.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Koichi Shiraishi (zchee)Thanks for tackling this. However, I don't think this is the right fix. Generic methods are not in the method set of a type; therefore the appropriate fix is in types.MethodSet.
Alan DonovanPerhaps this is due to the influence of https://go-review.googlesource.com/c/go/+/785740 revert?
Yes, we need to un-revert that change, but it was (I think) unfairly blamed for a bug in the api checker, which assumes that generic methods can be found in types.MethodSet; it needs to be fixed.
Like https://go-review.googlesource.com/c/go/+/787780? but it's need to un-revert https://go-review.googlesource.com/c/go/+/785740.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |