go/gcexportdata: limit reader to the export data section of the archive

When given an archive, NewReader can figure out the size of the export
data and there is no need to read the entire file, which includes
object code for the package in addition to export data.

We could make the same change in gcimporter.Import but choose not to,
as the function has no users.

Fixes golang/go#48954

Change-Id: I11e8e448c3133465f0e6ab01b3532da3732387af
Reviewed-on: https://go-review.googlesource.com/c/tools/+/357209
Trust: Dominik Honnef <dominik@honnef.co>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
diff --git a/go/gcexportdata/gcexportdata.go b/go/gcexportdata/gcexportdata.go
index fc8beea..cec819d6 100644
--- a/go/gcexportdata/gcexportdata.go
+++ b/go/gcexportdata/gcexportdata.go
@@ -50,11 +50,24 @@
 // additional trailing data beyond the end of the export data.
 func NewReader(r io.Reader) (io.Reader, error) {
 	buf := bufio.NewReader(r)
-	_, err := gcimporter.FindExportData(buf)
-	// If we ever switch to a zip-like archive format with the ToC
-	// at the end, we can return the correct portion of export data,
-	// but for now we must return the entire rest of the file.
-	return buf, err
+	_, size, err := gcimporter.FindExportData(buf)
+	if err != nil {
+		return nil, err
+	}
+
+	if size >= 0 {
+		// We were given an archive and found the __.PKGDEF in it.
+		// This tells us the size of the export data, and we don't
+		// need to return the entire file.
+		return &io.LimitedReader{
+			R: buf,
+			N: size,
+		}, nil
+	} else {
+		// We were given an object file. As such, we don't know how large
+		// the export data is and must return the entire file.
+		return buf, nil
+	}
 }
 
 // Read reads export data from in, decodes it, and returns type
diff --git a/go/internal/gcimporter/exportdata.go b/go/internal/gcimporter/exportdata.go
index f33dc56..f6437fe 100644
--- a/go/internal/gcimporter/exportdata.go
+++ b/go/internal/gcimporter/exportdata.go
@@ -16,7 +16,7 @@
 	"strings"
 )
 
-func readGopackHeader(r *bufio.Reader) (name string, size int, err error) {
+func readGopackHeader(r *bufio.Reader) (name string, size int64, err error) {
 	// See $GOROOT/include/ar.h.
 	hdr := make([]byte, 16+12+6+6+8+10+2)
 	_, err = io.ReadFull(r, hdr)
@@ -28,7 +28,8 @@
 		fmt.Printf("header: %s", hdr)
 	}
 	s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10]))
-	size, err = strconv.Atoi(s)
+	length, err := strconv.Atoi(s)
+	size = int64(length)
 	if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' {
 		err = fmt.Errorf("invalid archive header")
 		return
@@ -42,8 +43,8 @@
 // file by reading from it. The reader must be positioned at the
 // start of the file before calling this function. The hdr result
 // is the string before the export data, either "$$" or "$$B".
-//
-func FindExportData(r *bufio.Reader) (hdr string, err error) {
+// The size result is the length of the export data in bytes, or -1 if not known.
+func FindExportData(r *bufio.Reader) (hdr string, size int64, err error) {
 	// Read first line to make sure this is an object file.
 	line, err := r.ReadSlice('\n')
 	if err != nil {
@@ -54,7 +55,7 @@
 	if string(line) == "!<arch>\n" {
 		// Archive file. Scan to __.PKGDEF.
 		var name string
-		if name, _, err = readGopackHeader(r); err != nil {
+		if name, size, err = readGopackHeader(r); err != nil {
 			return
 		}
 
@@ -70,6 +71,7 @@
 			err = fmt.Errorf("can't find export data (%v)", err)
 			return
 		}
+		size -= int64(len(line))
 	}
 
 	// Now at __.PKGDEF in archive or still at beginning of file.
@@ -86,8 +88,12 @@
 			err = fmt.Errorf("can't find export data (%v)", err)
 			return
 		}
+		size -= int64(len(line))
 	}
 	hdr = string(line)
+	if size < 0 {
+		size = -1
+	}
 
 	return
 }
diff --git a/go/internal/gcimporter/gcimporter.go b/go/internal/gcimporter/gcimporter.go
index 519ef3b..3ab6683 100644
--- a/go/internal/gcimporter/gcimporter.go
+++ b/go/internal/gcimporter/gcimporter.go
@@ -185,7 +185,7 @@
 
 	var hdr string
 	buf := bufio.NewReader(rc)
-	if hdr, err = FindExportData(buf); err != nil {
+	if hdr, _, err = FindExportData(buf); err != nil {
 		return
 	}
 
diff --git a/go/internal/gcimporter/iexport_test.go b/go/internal/gcimporter/iexport_test.go
index 084679b..b326f30 100644
--- a/go/internal/gcimporter/iexport_test.go
+++ b/go/internal/gcimporter/iexport_test.go
@@ -42,7 +42,7 @@
 	defer f.Close()
 
 	buf := bufio.NewReader(f)
-	if _, err := gcimporter.FindExportData(buf); err != nil {
+	if _, _, err := gcimporter.FindExportData(buf); err != nil {
 		return nil, err
 	}