internal/gocore: remove Process.minorVersion

An explicit minor version number was added fairly recently in
https://go.dev/cl/321736. It seems like a nice simplification, but it
complicates working with devel builds significantly.

In particular, tests running via the coordinator (build.golang.org) have
no discernable version, just "devel SHA1", breaking version parsing, and
making it impossible to add a test for dynamically generated core files.

Thus, we drop minorVersion and revert back to feature tests via the type
system. There are two exceptions (both from 1.17) where the type system
isn't sufficient to differentiate the specific features, so
unfortunately we still need a heuristic to detect 1.17.

Change-Id: I5f98418833ee4742f1fe84de10865e133c9866f6
Reviewed-on: https://go-review.googlesource.com/c/debug/+/427214
Run-TryBot: Michael Pratt <mpratt@google.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/internal/gocore/module.go b/internal/gocore/module.go
index 5c81bf7..c0a55af 100644
--- a/internal/gocore/module.go
+++ b/internal/gocore/module.go
@@ -37,7 +37,8 @@
 	// Read the pc->function table
 	pcln := r.Field("pclntable")
 	var pctab, funcnametab region
-	if p.minorVersion >= 16 {
+	havePCtab := r.HasField("pctab")
+	if havePCtab {
 		// In 1.16, pclntable was split up into pctab and funcnametab.
 		pctab = r.Field("pctab")
 		funcnametab = r.Field("funcnametab")
@@ -48,7 +49,7 @@
 		ft := ftab.SliceIndex(i)
 		var min, max core.Address
 		var funcoff int64
-		if p.minorVersion >= 18 {
+		if ft.HasField("entryoff") {
 			min = m.textAddr(ft.Field("entryoff").Uint32())
 			max = m.textAddr(ftab.SliceIndex(i + 1).Field("entryoff").Uint32())
 			funcoff = int64(ft.Field("funcoff").Uint32())
@@ -62,7 +63,7 @@
 		}
 		fr := pcln.SliceIndex(funcoff).Cast("runtime._func")
 		var f *Func
-		if p.minorVersion >= 16 {
+		if havePCtab {
 			f = m.readFunc(fr, pctab, funcnametab)
 		} else {
 			f = m.readFunc(fr, pcln, pcln)
@@ -81,7 +82,7 @@
 // pcln must have type []byte and represent the module's pcln table region.
 func (m *module) readFunc(r region, pctab region, funcnametab region) *Func {
 	f := &Func{module: m, r: r}
-	if m.p.minorVersion >= 18 {
+	if r.HasField("entryoff") {
 		f.entry = m.textAddr(r.Field("entryoff").Uint32())
 	} else {
 		// Prior to 1.18, _func.entry directly referenced the entries.
@@ -90,7 +91,7 @@
 	f.name = r.p.proc.ReadCString(funcnametab.SliceIndex(int64(r.Field("nameoff").Int32())).a)
 	pcsp := r.Field("pcsp")
 	var pcspIdx int64
-	if m.p.minorVersion >= 16 {
+	if pcsp.typ.Kind == KindUint {
 		// In 1.16, pcsp changed to be a uint32 from an int32.
 		pcspIdx = int64(pcsp.Uint32())
 	} else {
@@ -102,7 +103,7 @@
 	// In 1.16, npcdata changed to be a uint32 from an int32.
 	npcdata := r.Field("npcdata")
 	var n uint32
-	if m.p.minorVersion >= 16 {
+	if npcdata.typ.Kind == KindUint {
 		// In 1.16, pcsp changed to be a uint32 from an int32.
 		n = uint32(npcdata.Uint32())
 	} else {
@@ -123,7 +124,7 @@
 		n = uint32(nfd.Int32())
 	}
 	for i := uint32(0); i < n; i++ {
-		if m.p.minorVersion >= 18 {
+		if m.r.HasField("gofunc") {
 			// Since 1.18, funcdata contains offsets from go.func.*.
 			off := r.p.proc.ReadUint32(a)
 			if off == ^uint32(0) {
diff --git a/internal/gocore/process.go b/internal/gocore/process.go
index 6bc6960..6cb8554 100644
--- a/internal/gocore/process.go
+++ b/internal/gocore/process.go
@@ -8,7 +8,6 @@
 	"debug/dwarf"
 	"fmt"
 	"math/bits"
-	"strconv"
 	"strings"
 	"sync"
 
@@ -55,7 +54,11 @@
 	stats *Stats
 
 	buildVersion string
-	minorVersion int
+
+	// This is a Go 1.17 process, or higher. This field is used for
+	// differences in behavior that otherwise can't be detected via the
+	// type system.
+	is117OrGreater bool
 
 	globals []*Root
 
@@ -155,14 +158,15 @@
 
 	// Read all the data that depend on runtime globals.
 	p.buildVersion = p.rtGlobals["buildVersion"].String()
-	versionComponents := strings.Split(p.buildVersion, ".")
-	if len(versionComponents) < 2 {
-		panic("malformed version " + p.buildVersion)
-	}
-	p.minorVersion, err = strconv.Atoi(versionComponents[1])
-	if err != nil {
-		panic("malformed version " + p.buildVersion)
-	}
+
+	// runtime._type varint name length encoding, and mheap curArena
+	// counting changed behavior in 1.17 without explicitly related type
+	// changes, making the difference difficult to detect. As a workaround,
+	// we check on the version explicitly.
+	//
+	// Go 1.17 added runtime._func.flag, so use that as a sentinal for this
+	// version.
+	p.is117OrGreater = p.findType("runtime._func").HasField("flag")
 
 	p.readModules()
 	p.readHeap()
@@ -333,12 +337,12 @@
 			panic("weird mapping " + m.Perm().String())
 		}
 	}
-	if p.minorVersion < 17 && mheap.HasField("curArena") {
-		// go1.13.3 and up has curArena.
-		// In Go 1.17, we ... don't need to do this any longer. See patch
-		// bd6aeca9686d5e672ffda1ea0cfeac7a3e7a20a4
-		// Subtract from the heap unallocated space
-		// in the current arena.
+	if !p.is117OrGreater && mheap.HasField("curArena") {
+		// 1.13.3 and up have curArena. Subtract unallocated space in
+		// the current arena from the heap.
+		//
+		// As of 1.17, the runtime does this automatically
+		// (https://go.dev/cl/270537).
 		ca := mheap.Field("curArena")
 		unused := int64(ca.Field("end").Uintptr() - ca.Field("base").Uintptr())
 		heap -= unused
diff --git a/internal/gocore/type.go b/internal/gocore/type.go
index 0397449..c8eb399 100644
--- a/internal/gocore/type.go
+++ b/internal/gocore/type.go
@@ -88,6 +88,10 @@
 	return nil
 }
 
+func (t *Type) HasField(name string) bool {
+	return t.field(name) != nil
+}
+
 // DynamicType returns the concrete type stored in the interface type t at address a.
 // If the interface is nil, returns nil.
 func (p *Process) DynamicType(t *Type, a core.Address) *Type {
@@ -114,7 +118,7 @@
 // return the number of bytes of the variable int and its value,
 // which means the length of a name.
 func readNameLen(p *Process, a core.Address) (int64, int64) {
-	if p.minorVersion >= 17 {
+	if p.is117OrGreater {
 		v := 0
 		for i := 0; ; i++ {
 			x := p.proc.ReadUint8(a.Add(int64(i + 1)))