diff --git a/src/io/multi.go b/src/io/multi.go index 46e45a60e8b7f3..d784846862c455 100644 --- a/src/io/multi.go +++ b/src/io/multi.go @@ -4,13 +4,19 @@ package io +type eofReader struct{} + +func (eofReader) Read([]byte) (int, error) { + return 0, EOF +} + type multiReader struct { readers []Reader } func (mr *multiReader) Read(p []byte) (n int, err error) { for len(mr.readers) > 0 { - // Optimization to flatten nested multiReaders (Issue 13558) + // Optimization to flatten nested multiReaders (Issue 13558). if len(mr.readers) == 1 { if r, ok := mr.readers[0].(*multiReader); ok { mr.readers = r.readers @@ -19,7 +25,9 @@ func (mr *multiReader) Read(p []byte) (n int, err error) { } n, err = mr.readers[0].Read(p) if err == EOF { - mr.readers[0] = nil // permit earlier GC + // Use eofReader instead of nil to avoid nil panic + // after performing flatten (Issue 18232). + mr.readers[0] = eofReader{} // permit earlier GC mr.readers = mr.readers[1:] } if n > 0 || err != EOF { diff --git a/src/io/multi_test.go b/src/io/multi_test.go index 16e351a87911ec..1a6292fa8ac2c5 100644 --- a/src/io/multi_test.go +++ b/src/io/multi_test.go @@ -264,3 +264,27 @@ func TestMultiReaderFreesExhaustedReaders(t *testing.T) { t.Fatalf(`ReadFull = %d (%q), %v; want 2, "ar", nil`, n, buf[:n], err) } } + +func TestInterleavedMultiReader(t *testing.T) { + r1 := strings.NewReader("123") + r2 := strings.NewReader("45678") + + mr1 := MultiReader(r1, r2) + mr2 := MultiReader(mr1) + + buf := make([]byte, 4) + + // Have mr2 use mr1's []Readers. + // Consume r1 (and clear it for GC to handle) and consume part of r2. + n, err := ReadFull(mr2, buf) + if got := string(buf[:n]); got != "1234" || err != nil { + t.Errorf(`ReadFull(mr2) = (%q, %v), want ("1234", nil)`, got, err) + } + + // Consume the rest of r2 via mr1. + // This should not panic even though mr2 cleared r1. + n, err = ReadFull(mr1, buf) + if got := string(buf[:n]); got != "5678" || err != nil { + t.Errorf(`ReadFull(mr1) = (%q, %v), want ("5678", nil)`, got, err) + } +}