internal/gocore: update to handle 1.12 core files

Just a few adjustments to handle runtime changes.

Use zip files for test data so we don't use so much
disk space for large core files. See golang/go#28081.

Fixes golang/go#30631

Change-Id: I6a11aae56e715540fc320cbe70e426eeafa2f64d
Reviewed-on: https://go-review.googlesource.com/c/debug/+/175497
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/internal/gocore/gocore_test.go b/internal/gocore/gocore_test.go
index dac1cb1..b1567a3 100644
--- a/internal/gocore/gocore_test.go
+++ b/internal/gocore/gocore_test.go
@@ -7,8 +7,15 @@
 package gocore
 
 import (
+	"archive/zip"
 	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
 	"reflect"
+	"strings"
 	"testing"
 
 	"golang.org/x/debug/internal/core"
@@ -36,7 +43,26 @@
 	if version == "1.9" {
 		version = ""
 	}
-	c, err := core.Core(fmt.Sprintf("testdata/core%s", version), "testdata", "")
+	var file string
+	var base string
+	if strings.HasSuffix(version, ".zip") {
+		// Make temporary directory.
+		dir, err := ioutil.TempDir("", strings.TrimSuffix(version, ".zip")+"_")
+		if err != nil {
+			t.Fatalf("can't make temp directory: %s", err)
+		}
+		defer os.RemoveAll(dir)
+
+		// Unpack test into directory.
+		unzip(t, filepath.Join("testdata", version), dir)
+
+		file = filepath.Join(dir, "tmp", "coretest", "core")
+		base = dir
+	} else {
+		file = fmt.Sprintf("testdata/core%s", version)
+		base = "testdata"
+	}
+	c, err := core.Core(file, base, "")
 	if err != nil {
 		t.Fatalf("can't load test core file: %s", err)
 	}
@@ -47,6 +73,40 @@
 	return p
 }
 
+// unzip unpacks the zip file name into the directory dir.
+func unzip(t *testing.T, name, dir string) {
+	r, err := zip.OpenReader(name)
+	if err != nil {
+		t.Fatalf("can't read zip file %s: %s", name, err)
+	}
+	for _, f := range r.File {
+		rf, err := f.Open()
+		if err != nil {
+			t.Fatalf("can't read entry %s: %s", f.Name, err)
+		}
+		err = os.MkdirAll(path.Dir(filepath.Join(dir, f.Name)), 0777)
+		if err != nil {
+			t.Fatalf("can't make directory: %s", err)
+		}
+		wf, err := os.Create(filepath.Join(dir, f.Name))
+		if err != nil {
+			t.Fatalf("can't write entry %s: %s", f.Name, err)
+		}
+		_, err = io.Copy(wf, rf)
+		if err != nil {
+			t.Fatalf("can't copy %s: %s", f.Name, err)
+		}
+		err = rf.Close()
+		if err != nil {
+			t.Fatalf("can't close reader %s: %s", f.Name, err)
+		}
+		err = wf.Close()
+		if err != nil {
+			t.Fatalf("can't close writer %s: %s", f.Name, err)
+		}
+	}
+}
+
 func TestObjects(t *testing.T) {
 	p := loadExample(t)
 	n := 0
@@ -197,4 +257,5 @@
 func TestVersions(t *testing.T) {
 	loadExampleVersion(t, "1.10")
 	loadExampleVersion(t, "1.11")
+	loadExampleVersion(t, "1.12.zip")
 }
diff --git a/internal/gocore/module.go b/internal/gocore/module.go
index 85c3826..2f5e632 100644
--- a/internal/gocore/module.go
+++ b/internal/gocore/module.go
@@ -69,7 +69,12 @@
 		a = a.Add(4)
 	}
 	a = a.Align(r.p.proc.PtrSize())
-	n = r.Field("nfuncdata").Int32()
+
+	if nfd := r.Field("nfuncdata"); nfd.typ.Size == 1 { // go 1.12 and beyond, this is a uint8
+		n = int32(nfd.Uint8())
+	} else { // go 1.11 and earlier, this is an int32
+		n = nfd.Int32()
+	}
 	for i := int32(0); i < n; i++ {
 		f.funcdata = append(f.funcdata, r.p.proc.ReadPtr(a))
 		a = a.Add(r.p.proc.PtrSize())
diff --git a/internal/gocore/process.go b/internal/gocore/process.go
index 05c7c84..a3fe7b1 100644
--- a/internal/gocore/process.go
+++ b/internal/gocore/process.go
@@ -400,8 +400,14 @@
 			}
 		case spanFree:
 			freeSpanSize += spanSize
-			nReleased := int64(s.Field("npreleased").Uintptr())
-			releasedSpanSize += nReleased * pageSize
+			if s.HasField("npreleased") { // go 1.11 and earlier
+				nReleased := int64(s.Field("npreleased").Uintptr())
+				releasedSpanSize += nReleased * pageSize
+			} else { // go 1.12 and beyond
+				if s.Field("scavenged").Bool() {
+					releasedSpanSize += spanSize
+				}
+			}
 		case spanDead:
 			// These are just deallocated span descriptors. They use no heap.
 		case spanManual:
diff --git a/internal/gocore/region.go b/internal/gocore/region.go
index a8145d4..e4fd59a 100644
--- a/internal/gocore/region.go
+++ b/internal/gocore/region.go
@@ -102,6 +102,15 @@
 	return r.p.proc.ReadUint8(r.a)
 }
 
+// Bool returns the bool value stored in r.
+// r must have type bool.
+func (r region) Bool() bool {
+	if r.typ.Kind != KindBool {
+		panic("bad bool type " + r.typ.Name)
+	}
+	return r.p.proc.ReadUint8(r.a) != 0
+}
+
 // String returns the value of the string stored in r.
 func (r region) String() string {
 	if r.typ.Kind != KindString {
diff --git a/internal/gocore/testdata/1.12.zip b/internal/gocore/testdata/1.12.zip
new file mode 100644
index 0000000..4d2a74a
--- /dev/null
+++ b/internal/gocore/testdata/1.12.zip
Binary files differ
diff --git a/internal/gocore/testdata/README b/internal/gocore/testdata/README
index e3922dc..af06176 100644
--- a/internal/gocore/testdata/README
+++ b/internal/gocore/testdata/README
@@ -15,3 +15,18 @@
 
 core1.10 is produced in the same way, except using go1.10.0.
 core1.11 is produced in the same way, except using go1.11.0.
+
+
+steps for subsequent versions:
+
+mkdir /tmp/coretest
+cd /tmp/coretest
+rm -fr *   // in case there's old junk there
+cat the above program into test.go
+go build test.go
+ulimit -c unlimited
+GOTRACEBACK=crash ./test
+zip 1.12.zip /tmp/coretest/core /tmp/coretest/test  // use your version number
+
+Then move the 1.12.zip to this directory.
+Add a new test to TestVersions in ../gocore_test.go.