src/bufio/bufio.go GO 843 lines View on github.com → Search inside
1// Copyright 2009 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45// Package bufio implements buffered I/O. It wraps an io.Reader or io.Writer6// object, creating another object (Reader or Writer) that also implements7// the interface but provides buffering and some help for textual I/O.8package bufio910import (11	"bytes"12	"errors"13	"io"14	"strings"15	"unicode/utf8"16)1718const (19	defaultBufSize = 409620)2122var (23	ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")24	ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")25	ErrBufferFull        = errors.New("bufio: buffer full")26	ErrNegativeCount     = errors.New("bufio: negative count")27)2829// Buffered input.3031// Reader implements buffering for an io.Reader object.32// A new Reader is created by calling [NewReader] or [NewReaderSize];33// alternatively the zero value of a Reader may be used after calling [Reader.Reset]34// on it.35type Reader struct {36	buf          []byte37	rd           io.Reader // reader provided by the client38	r, w         int       // buf read and write positions39	err          error40	lastByte     int // last byte read for UnreadByte; -1 means invalid41	lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid42}4344const minReadBufferSize = 1645const maxConsecutiveEmptyReads = 1004647// NewReaderSize returns a new [Reader] whose buffer has at least the specified48// size. If the argument io.Reader is already a [Reader] with large enough49// size, it returns the underlying [Reader].50func NewReaderSize(rd io.Reader, size int) *Reader {51	// Is it already a Reader?52	b, ok := rd.(*Reader)53	if ok && len(b.buf) >= size {54		return b55	}56	r := new(Reader)57	r.reset(make([]byte, max(size, minReadBufferSize)), rd)58	return r59}6061// NewReader returns a new [Reader] whose buffer has the default size.62func NewReader(rd io.Reader) *Reader {63	return NewReaderSize(rd, defaultBufSize)64}6566// Size returns the size of the underlying buffer in bytes.67func (b *Reader) Size() int { return len(b.buf) }6869// Reset discards any buffered data, resets all state, and switches70// the buffered reader to read from r.71// Calling Reset on the zero value of [Reader] initializes the internal buffer72// to the default size.73// Calling b.Reset(b) (that is, resetting a [Reader] to itself) does nothing.74func (b *Reader) Reset(r io.Reader) {75	// If a Reader r is passed to NewReader, NewReader will return r.76	// Different layers of code may do that, and then later pass r77	// to Reset. Avoid infinite recursion in that case.78	if b == r {79		return80	}81	if b.buf == nil {82		b.buf = make([]byte, defaultBufSize)83	}84	b.reset(b.buf, r)85}8687func (b *Reader) reset(buf []byte, r io.Reader) {88	*b = Reader{89		buf:          buf,90		rd:           r,91		lastByte:     -1,92		lastRuneSize: -1,93	}94}9596var errNegativeRead = errors.New("bufio: reader returned negative count from Read")9798// fill reads a new chunk into the buffer.99func (b *Reader) fill() {100	// Slide existing data to beginning.101	if b.r > 0 {102		copy(b.buf, b.buf[b.r:b.w])103		b.w -= b.r104		b.r = 0105	}106107	if b.w >= len(b.buf) {108		panic("bufio: tried to fill full buffer")109	}110111	// Read new data: try a limited number of times.112	for i := maxConsecutiveEmptyReads; i > 0; i-- {113		n, err := b.rd.Read(b.buf[b.w:])114		if n < 0 {115			panic(errNegativeRead)116		}117		b.w += n118		if err != nil {119			b.err = err120			return121		}122		if n > 0 {123			return124		}125	}126	b.err = io.ErrNoProgress127}128129func (b *Reader) readErr() error {130	err := b.err131	b.err = nil132	return err133}134135// Peek returns the next n bytes without advancing the reader. The bytes stop136// being valid at the next read call. If necessary, Peek will read more bytes137// into the buffer in order to make n bytes available. If Peek returns fewer138// than n bytes, it also returns an error explaining why the read is short.139// The error is [ErrBufferFull] if n is larger than b's buffer size.140//141// Calling Peek prevents a [Reader.UnreadByte] or [Reader.UnreadRune] call from succeeding142// until the next read operation.143func (b *Reader) Peek(n int) ([]byte, error) {144	if n < 0 {145		return nil, ErrNegativeCount146	}147148	b.lastByte = -1149	b.lastRuneSize = -1150151	for b.w-b.r < n && b.w-b.r < len(b.buf) && b.err == nil {152		b.fill() // b.w-b.r < len(b.buf) => buffer is not full153	}154155	if n > len(b.buf) {156		return b.buf[b.r:b.w], ErrBufferFull157	}158159	// 0 <= n <= len(b.buf)160	var err error161	if avail := b.w - b.r; avail < n {162		// not enough data in buffer163		n = avail164		err = b.readErr()165		if err == nil {166			err = ErrBufferFull167		}168	}169	return b.buf[b.r : b.r+n], err170}171172// Discard skips the next n bytes, returning the number of bytes discarded.173//174// If Discard skips fewer than n bytes, it also returns an error.175// If 0 <= n <= b.Buffered(), Discard is guaranteed to succeed without176// reading from the underlying io.Reader.177func (b *Reader) Discard(n int) (discarded int, err error) {178	if n < 0 {179		return 0, ErrNegativeCount180	}181	if n == 0 {182		return183	}184185	b.lastByte = -1186	b.lastRuneSize = -1187188	remain := n189	for {190		skip := b.Buffered()191		if skip == 0 {192			b.fill()193			skip = b.Buffered()194		}195		if skip > remain {196			skip = remain197		}198		b.r += skip199		remain -= skip200		if remain == 0 {201			return n, nil202		}203		if b.err != nil {204			return n - remain, b.readErr()205		}206	}207}208209// Read reads data into p.210// It returns the number of bytes read into p.211// The bytes are taken from at most one Read on the underlying [Reader],212// hence n may be less than len(p).213// To read exactly len(p) bytes, use io.ReadFull(b, p).214// If the underlying [Reader] can return a non-zero count with io.EOF,215// then this Read method can do so as well; see the [io.Reader] docs.216func (b *Reader) Read(p []byte) (n int, err error) {217	n = len(p)218	if n == 0 {219		if b.Buffered() > 0 {220			return 0, nil221		}222		return 0, b.readErr()223	}224	if b.r == b.w {225		if b.err != nil {226			return 0, b.readErr()227		}228		if len(p) >= len(b.buf) {229			// Large read, empty buffer.230			// Read directly into p to avoid copy.231			n, b.err = b.rd.Read(p)232			if n < 0 {233				panic(errNegativeRead)234			}235			if n > 0 {236				b.lastByte = int(p[n-1])237				b.lastRuneSize = -1238			}239			return n, b.readErr()240		}241		// One read.242		// Do not use b.fill, which will loop.243		b.r = 0244		b.w = 0245		n, b.err = b.rd.Read(b.buf)246		if n < 0 {247			panic(errNegativeRead)248		}249		if n == 0 {250			return 0, b.readErr()251		}252		b.w += n253	}254255	// copy as much as we can256	// Note: if the slice panics here, it is probably because257	// the underlying reader returned a bad count. See issue 49795.258	n = copy(p, b.buf[b.r:b.w])259	b.r += n260	b.lastByte = int(b.buf[b.r-1])261	b.lastRuneSize = -1262	return n, nil263}264265// ReadByte reads and returns a single byte.266// If no byte is available, returns an error.267func (b *Reader) ReadByte() (byte, error) {268	b.lastRuneSize = -1269	for b.r == b.w {270		if b.err != nil {271			return 0, b.readErr()272		}273		b.fill() // buffer is empty274	}275	c := b.buf[b.r]276	b.r++277	b.lastByte = int(c)278	return c, nil279}280281// UnreadByte unreads the last byte. Only the most recently read byte can be unread.282//283// UnreadByte returns an error if the most recent method called on the284// [Reader] was not a read operation. Notably, [Reader.Peek], [Reader.Discard], and [Reader.WriteTo] are not285// considered read operations.286func (b *Reader) UnreadByte() error {287	if b.lastByte < 0 || b.r == 0 && b.w > 0 {288		return ErrInvalidUnreadByte289	}290	// b.r > 0 || b.w == 0291	if b.r > 0 {292		b.r--293	} else {294		// b.r == 0 && b.w == 0295		b.w = 1296	}297	b.buf[b.r] = byte(b.lastByte)298	b.lastByte = -1299	b.lastRuneSize = -1300	return nil301}302303// ReadRune reads a single UTF-8 encoded Unicode character and returns the304// rune and its size in bytes. If the encoded rune is invalid, it consumes one byte305// and returns unicode.ReplacementChar (U+FFFD) with a size of 1.306func (b *Reader) ReadRune() (r rune, size int, err error) {307	for b.r+utf8.UTFMax > b.w && !utf8.FullRune(b.buf[b.r:b.w]) && b.err == nil && b.w-b.r < len(b.buf) {308		b.fill() // b.w-b.r < len(buf) => buffer is not full309	}310	b.lastRuneSize = -1311	if b.r == b.w {312		return 0, 0, b.readErr()313	}314	r, size = utf8.DecodeRune(b.buf[b.r:b.w])315	b.r += size316	b.lastByte = int(b.buf[b.r-1])317	b.lastRuneSize = size318	return r, size, nil319}320321// UnreadRune unreads the last rune. If the most recent method called on322// the [Reader] was not a [Reader.ReadRune], [Reader.UnreadRune] returns an error. (In this323// regard it is stricter than [Reader.UnreadByte], which will unread the last byte324// from any read operation.)325func (b *Reader) UnreadRune() error {326	if b.lastRuneSize < 0 || b.r < b.lastRuneSize {327		return ErrInvalidUnreadRune328	}329	b.r -= b.lastRuneSize330	b.lastByte = -1331	b.lastRuneSize = -1332	return nil333}334335// Buffered returns the number of bytes that can be read from the current buffer.336func (b *Reader) Buffered() int { return b.w - b.r }337338// ReadSlice reads until the first occurrence of delim in the input,339// returning a slice pointing at the bytes in the buffer.340// The bytes stop being valid at the next read.341// If ReadSlice encounters an error before finding a delimiter,342// it returns all the data in the buffer and the error itself (often io.EOF).343// ReadSlice fails with error [ErrBufferFull] if the buffer fills without a delim.344// Because the data returned from ReadSlice will be overwritten345// by the next I/O operation, most clients should use346// [Reader.ReadBytes] or ReadString instead.347// ReadSlice returns err != nil if and only if line does not end in delim.348func (b *Reader) ReadSlice(delim byte) (line []byte, err error) {349	s := 0 // search start index350	for {351		// Search buffer.352		if i := bytes.IndexByte(b.buf[b.r+s:b.w], delim); i >= 0 {353			i += s354			line = b.buf[b.r : b.r+i+1]355			b.r += i + 1356			break357		}358359		// Pending error?360		if b.err != nil {361			line = b.buf[b.r:b.w]362			b.r = b.w363			err = b.readErr()364			break365		}366367		// Buffer full?368		if b.Buffered() >= len(b.buf) {369			b.r = b.w370			line = b.buf371			err = ErrBufferFull372			break373		}374375		s = b.w - b.r // do not rescan area we scanned before376377		b.fill() // buffer is not full378	}379380	// Handle last byte, if any.381	if i := len(line) - 1; i >= 0 {382		b.lastByte = int(line[i])383		b.lastRuneSize = -1384	}385386	return387}388389// ReadLine is a low-level line-reading primitive. Most callers should use390// [Reader.ReadBytes]('\n') or [Reader.ReadString]('\n') instead or use a [Scanner].391//392// ReadLine tries to return a single line, not including the end-of-line bytes.393// If the line was too long for the buffer then isPrefix is set and the394// beginning of the line is returned. The rest of the line will be returned395// from future calls. isPrefix will be false when returning the last fragment396// of the line. The returned buffer is only valid until the next call to397// ReadLine. ReadLine either returns a non-nil line or it returns an error,398// never both.399//400// The text returned from ReadLine does not include the line end ("\r\n" or "\n").401// No indication or error is given if the input ends without a final line end.402// Calling [Reader.UnreadByte] after ReadLine will always unread the last byte read403// (possibly a character belonging to the line end) even if that byte is not404// part of the line returned by ReadLine.405func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) {406	line, err = b.ReadSlice('\n')407	if err == ErrBufferFull {408		// Handle the case where "\r\n" straddles the buffer.409		if len(line) > 0 && line[len(line)-1] == '\r' {410			// Put the '\r' back on buf and drop it from line.411			// Let the next call to ReadLine check for "\r\n".412			if b.r == 0 {413				// should be unreachable414				panic("bufio: tried to rewind past start of buffer")415			}416			b.r--417			line = line[:len(line)-1]418		}419		return line, true, nil420	}421422	if len(line) == 0 {423		if err != nil {424			line = nil425		}426		return427	}428	err = nil429430	if line[len(line)-1] == '\n' {431		drop := 1432		if len(line) > 1 && line[len(line)-2] == '\r' {433			drop = 2434		}435		line = line[:len(line)-drop]436	}437	return438}439440// collectFragments reads until the first occurrence of delim in the input. It441// returns (slice of full buffers, remaining bytes before delim, total number442// of bytes in the combined first two elements, error).443// The complete result is equal to444// `bytes.Join(append(fullBuffers, finalFragment), nil)`, which has a445// length of `totalLen`. The result is structured in this way to allow callers446// to minimize allocations and copies.447func (b *Reader) collectFragments(delim byte) (fullBuffers [][]byte, finalFragment []byte, totalLen int, err error) {448	var frag []byte449	// Use ReadSlice to look for delim, accumulating full buffers.450	for {451		var e error452		frag, e = b.ReadSlice(delim)453		if e == nil { // got final fragment454			break455		}456		if e != ErrBufferFull { // unexpected error457			err = e458			break459		}460461		// Make a copy of the buffer.462		buf := bytes.Clone(frag)463		fullBuffers = append(fullBuffers, buf)464		totalLen += len(buf)465	}466467	totalLen += len(frag)468	return fullBuffers, frag, totalLen, err469}470471// ReadBytes reads until the first occurrence of delim in the input,472// returning a slice containing the data up to and including the delimiter.473// If ReadBytes encounters an error before finding a delimiter,474// it returns the data read before the error and the error itself (often io.EOF).475// ReadBytes returns err != nil if and only if the returned data does not end in476// delim.477// For simple uses, a Scanner may be more convenient.478func (b *Reader) ReadBytes(delim byte) ([]byte, error) {479	full, frag, n, err := b.collectFragments(delim)480	// Allocate new buffer to hold the full pieces and the fragment.481	buf := make([]byte, n)482	n = 0483	// Copy full pieces and fragment in.484	for i := range full {485		n += copy(buf[n:], full[i])486	}487	copy(buf[n:], frag)488	return buf, err489}490491// ReadString reads until the first occurrence of delim in the input,492// returning a string containing the data up to and including the delimiter.493// If ReadString encounters an error before finding a delimiter,494// it returns the data read before the error and the error itself (often io.EOF).495// ReadString returns err != nil if and only if the returned data does not end in496// delim.497// For simple uses, a Scanner may be more convenient.498func (b *Reader) ReadString(delim byte) (string, error) {499	full, frag, n, err := b.collectFragments(delim)500	// Allocate new buffer to hold the full pieces and the fragment.501	var buf strings.Builder502	buf.Grow(n)503	// Copy full pieces and fragment in.504	for _, fb := range full {505		buf.Write(fb)506	}507	buf.Write(frag)508	return buf.String(), err509}510511// WriteTo implements io.WriterTo.512// This may make multiple calls to the [Reader.Read] method of the underlying [Reader].513// If the underlying reader supports the [Reader.WriteTo] method,514// this calls the underlying [Reader.WriteTo] without buffering.515func (b *Reader) WriteTo(w io.Writer) (n int64, err error) {516	b.lastByte = -1517	b.lastRuneSize = -1518519	if b.r < b.w {520		n, err = b.writeBuf(w)521		if err != nil {522			return523		}524	}525526	if r, ok := b.rd.(io.WriterTo); ok {527		m, err := r.WriteTo(w)528		n += m529		return n, err530	}531532	if w, ok := w.(io.ReaderFrom); ok {533		m, err := w.ReadFrom(b.rd)534		n += m535		return n, err536	}537538	if b.w-b.r < len(b.buf) {539		b.fill() // buffer not full540	}541542	for b.r < b.w {543		// b.r < b.w => buffer is not empty544		m, err := b.writeBuf(w)545		n += m546		if err != nil {547			return n, err548		}549		b.fill() // buffer is empty550	}551552	if b.err == io.EOF {553		b.err = nil554	}555556	return n, b.readErr()557}558559var errNegativeWrite = errors.New("bufio: writer returned negative count from Write")560561// writeBuf writes the [Reader]'s buffer to the writer.562func (b *Reader) writeBuf(w io.Writer) (int64, error) {563	n, err := w.Write(b.buf[b.r:b.w])564	if n < 0 {565		panic(errNegativeWrite)566	}567	b.r += n568	return int64(n), err569}570571// buffered output572573// Writer implements buffering for an [io.Writer] object.574// If an error occurs writing to a [Writer], no more data will be575// accepted and all subsequent writes, and [Writer.Flush], will return the error.576// After all data has been written, the client should call the577// [Writer.Flush] method to guarantee all data has been forwarded to578// the underlying [io.Writer].579type Writer struct {580	err error581	buf []byte582	n   int583	wr  io.Writer584}585586// NewWriterSize returns a new [Writer] whose buffer has at least the specified587// size. If the argument io.Writer is already a [Writer] with large enough588// size, it returns the underlying [Writer].589func NewWriterSize(w io.Writer, size int) *Writer {590	// Is it already a Writer?591	b, ok := w.(*Writer)592	if ok && len(b.buf) >= size {593		return b594	}595	if size <= 0 {596		size = defaultBufSize597	}598	return &Writer{599		buf: make([]byte, size),600		wr:  w,601	}602}603604// NewWriter returns a new [Writer] whose buffer has the default size.605// If the argument io.Writer is already a [Writer] with large enough buffer size,606// it returns the underlying [Writer].607func NewWriter(w io.Writer) *Writer {608	return NewWriterSize(w, defaultBufSize)609}610611// Size returns the size of the underlying buffer in bytes.612func (b *Writer) Size() int { return len(b.buf) }613614// Reset discards any unflushed buffered data, clears any error, and615// resets b to write its output to w.616// Calling Reset on the zero value of [Writer] initializes the internal buffer617// to the default size.618// Calling w.Reset(w) (that is, resetting a [Writer] to itself) does nothing.619func (b *Writer) Reset(w io.Writer) {620	// If a Writer w is passed to NewWriter, NewWriter will return w.621	// Different layers of code may do that, and then later pass w622	// to Reset. Avoid infinite recursion in that case.623	if b == w {624		return625	}626	if b.buf == nil {627		b.buf = make([]byte, defaultBufSize)628	}629	b.err = nil630	b.n = 0631	b.wr = w632}633634// Flush writes any buffered data to the underlying [io.Writer].635func (b *Writer) Flush() error {636	if b.err != nil {637		return b.err638	}639	if b.n == 0 {640		return nil641	}642	n, err := b.wr.Write(b.buf[0:b.n])643	if n < b.n && err == nil {644		err = io.ErrShortWrite645	}646	if err != nil {647		if n > 0 && n < b.n {648			copy(b.buf[0:b.n-n], b.buf[n:b.n])649		}650		b.n -= n651		b.err = err652		return err653	}654	b.n = 0655	return nil656}657658// Available returns how many bytes are unused in the buffer.659func (b *Writer) Available() int { return len(b.buf) - b.n }660661// AvailableBuffer returns an empty buffer with b.Available() capacity.662// This buffer is intended to be appended to and663// passed to an immediately succeeding [Writer.Write] call.664// The buffer is only valid until the next write operation on b.665func (b *Writer) AvailableBuffer() []byte {666	return b.buf[b.n:][:0]667}668669// Buffered returns the number of bytes that have been written into the current buffer.670func (b *Writer) Buffered() int { return b.n }671672// Write writes the contents of p into the buffer.673// It returns the number of bytes written.674// If nn < len(p), it also returns an error explaining675// why the write is short.676func (b *Writer) Write(p []byte) (nn int, err error) {677	for len(p) > b.Available() && b.err == nil {678		var n int679		if b.Buffered() == 0 {680			// Large write, empty buffer.681			// Write directly from p to avoid copy.682			n, b.err = b.wr.Write(p)683		} else {684			n = copy(b.buf[b.n:], p)685			b.n += n686			b.Flush()687		}688		nn += n689		p = p[n:]690	}691	if b.err != nil {692		return nn, b.err693	}694	n := copy(b.buf[b.n:], p)695	b.n += n696	nn += n697	return nn, nil698}699700// WriteByte writes a single byte.701func (b *Writer) WriteByte(c byte) error {702	if b.err != nil {703		return b.err704	}705	if b.Available() <= 0 && b.Flush() != nil {706		return b.err707	}708	b.buf[b.n] = c709	b.n++710	return nil711}712713// WriteRune writes a single Unicode code point, returning714// the number of bytes written and any error.715func (b *Writer) WriteRune(r rune) (size int, err error) {716	// Compare as uint32 to correctly handle negative runes.717	if uint32(r) < utf8.RuneSelf {718		err = b.WriteByte(byte(r))719		if err != nil {720			return 0, err721		}722		return 1, nil723	}724	if b.err != nil {725		return 0, b.err726	}727	n := b.Available()728	if n < utf8.UTFMax {729		if b.Flush(); b.err != nil {730			return 0, b.err731		}732		n = b.Available()733		if n < utf8.UTFMax {734			// Can only happen if buffer is silly small.735			return b.WriteString(string(r))736		}737	}738	size = utf8.EncodeRune(b.buf[b.n:], r)739	b.n += size740	return size, nil741}742743// WriteString writes a string.744// It returns the number of bytes written.745// If the count is less than len(s), it also returns an error explaining746// why the write is short.747func (b *Writer) WriteString(s string) (int, error) {748	var sw io.StringWriter749	tryStringWriter := true750751	nn := 0752	for len(s) > b.Available() && b.err == nil {753		var n int754		if b.Buffered() == 0 && sw == nil && tryStringWriter {755			// Check at most once whether b.wr is a StringWriter.756			sw, tryStringWriter = b.wr.(io.StringWriter)757		}758		if b.Buffered() == 0 && tryStringWriter {759			// Large write, empty buffer, and the underlying writer supports760			// WriteString: forward the write to the underlying StringWriter.761			// This avoids an extra copy.762			n, b.err = sw.WriteString(s)763		} else {764			n = copy(b.buf[b.n:], s)765			b.n += n766			b.Flush()767		}768		nn += n769		s = s[n:]770	}771	if b.err != nil {772		return nn, b.err773	}774	n := copy(b.buf[b.n:], s)775	b.n += n776	nn += n777	return nn, nil778}779780// ReadFrom implements [io.ReaderFrom]. If the underlying writer781// supports the ReadFrom method, this calls the underlying ReadFrom.782// If there is buffered data and an underlying ReadFrom, this fills783// the buffer and writes it before calling ReadFrom.784func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {785	if b.err != nil {786		return 0, b.err787	}788	readerFrom, readerFromOK := b.wr.(io.ReaderFrom)789	var m int790	for {791		if b.Available() == 0 {792			if err1 := b.Flush(); err1 != nil {793				return n, err1794			}795		}796		if readerFromOK && b.Buffered() == 0 {797			nn, err := readerFrom.ReadFrom(r)798			b.err = err799			n += nn800			return n, err801		}802		nr := 0803		for nr < maxConsecutiveEmptyReads {804			m, err = r.Read(b.buf[b.n:])805			if m != 0 || err != nil {806				break807			}808			nr++809		}810		if nr == maxConsecutiveEmptyReads {811			return n, io.ErrNoProgress812		}813		b.n += m814		n += int64(m)815		if err != nil {816			break817		}818	}819	if err == io.EOF {820		// If we filled the buffer exactly, flush preemptively.821		if b.Available() == 0 {822			err = b.Flush()823		} else {824			err = nil825		}826	}827	return n, err828}829830// buffered input and output831832// ReadWriter stores pointers to a [Reader] and a [Writer].833// It implements [io.ReadWriter].834type ReadWriter struct {835	*Reader836	*Writer837}838839// NewReadWriter allocates a new [ReadWriter] that dispatches to r and w.840func NewReadWriter(r *Reader, w *Writer) *ReadWriter {841	return &ReadWriter{r, w}842}

Code quality findings 3

Ensure errors are handled or logged
warning correctness unhandled-error
if err != nil {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
// `bytes.Join(append(fullBuffers, finalFragment), nil)`, which has a
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
fullBuffers = append(fullBuffers, buf)

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.