{"record":{"id":"5d60966a6a46c6ec","repo":"golang/go","slug":"bytes-buffer-truncation-out-of-range","errorCode":null,"errorMessage":"bytes.Buffer: truncation out of range","messagePattern":"bytes\\.Buffer: truncation out of range","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/bytes/buffer.go","lineNumber":116,"sourceCode":"\n// Cap returns the capacity of the buffer's underlying byte slice, that is, the\n// total space allocated for the buffer's data.\nfunc (b *Buffer) Cap() int { return cap(b.buf) }\n\n// Available returns how many bytes are unused in the buffer.\nfunc (b *Buffer) Available() int { return cap(b.buf) - len(b.buf) }\n\n// Truncate discards all but the first n unread bytes from the buffer\n// but continues to use the same allocated storage.\n// It panics if n is negative or greater than the length of the buffer.\nfunc (b *Buffer) Truncate(n int) {\n\tif n == 0 {\n\t\tb.Reset()\n\t\treturn\n\t}\n\tb.lastRead = opInvalid\n\tif n < 0 || n > b.Len() {\n\t\tpanic(\"bytes.Buffer: truncation out of range\")\n\t}\n\tb.buf = b.buf[:b.off+n]\n}\n\n// Reset resets the buffer to be empty,\n// but it retains the underlying storage for use by future writes.\n// Reset is the same as [Buffer.Truncate](0).\nfunc (b *Buffer) Reset() {\n\tb.buf = b.buf[:0]\n\tb.off = 0\n\tb.lastRead = opInvalid\n}\n\n// tryGrowByReslice is an inlineable version of grow for the fast-case where the\n// internal buffer only needs to be resliced.\n// It returns the index where bytes should be written and whether it succeeded.\nfunc (b *Buffer) tryGrowByReslice(n int) (int, bool) {\n\tif l := len(b.buf); n <= cap(b.buf)-l {","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/bytes/buffer.go#L98-L134","documentation":"bytes.Buffer.Truncate(n) discards all but the first n UNREAD bytes of the buffer (the unread portion is len(b.buf) - b.off, exposed as b.Len()). It panics with \"bytes.Buffer: truncation out of range\" when n < 0 or n > b.Len(). Because Truncate operates on the unread region (past b.off), passing a length computed against the whole backing slice or against a pre-read length will trigger the panic even when the intent looks safe.","triggerScenarios":"Calling b.Truncate(n) with n negative; calling b.Truncate(n) where n exceeds the current unread length b.Len() (e.g. after bytes have already been consumed by Read/Next); passing len(b.Bytes()) (which is the unread slice length but reflects a stale view) after intermediate mutations; computing n from the original write length rather than from b.Len().","commonSituations":"Off-by-one when trimming a trailing delimiter or newline (n = len(data) - 1 while data was already partially read); mixing manual b.buf slicing with the Buffer API so b.off and b.Len() drift; using Truncate to implement an \"undo last write\" where n is taken from a variable updated before Read; streaming parsers that Truncate to a computed offset after consuming a header.","solutions":["Guard with the live unread length: if n >= 0 && n <= b.Len() { b.Truncate(n) } else { /* handle */ }.","Recompute n from b.Len() (the unread count) immediately before the call, not from a length captured earlier.","If the goal is to drop the whole unread buffer, call b.Reset() instead of Truncate(0) (Truncate already special-cases 0 to Reset, but Reset makes intent explicit and avoids the range check entirely).","If you need to keep the whole backing array but reset logical position, prefer Reset over a Truncate derived from stale state."],"exampleFix":"// before\nb.Write(data)\nb.Read(prefix)\nb.Truncate(len(data) - len(prefix)) // len(data) is stale; may exceed b.Len()\n\n// after\nb.Write(data)\nb.Read(prefix)\nn := len(data) - len(prefix)\nif n < 0 {\n    n = 0\n}\nif n > b.Len() {\n    n = b.Len()\n}\nb.Truncate(n)","handlingStrategy":"validation","validationCode":"// Validate before Truncate: n must be within [0, b.Len()].\nfunc safeTruncate(b *bytes.Buffer, n int) error {\n    if n < 0 || n > b.Len() {\n        return fmt.Errorf(\"truncate %d out of range [0,%d]\", n, b.Len())\n    }\n    b.Truncate(n)\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always derive n from b.Len() (the unread length), never from the backing slice length or a stale counter.","Clamp computed n to [0, b.Len()] before calling Truncate.","Use b.Reset() when you want to discard everything; it skips the range check.","Avoid interleaving manual b.buf slicing with the Buffer API, which desynchronizes b.off and b.Len()."],"tags":["bytes","buffer","validation","panic","go","truncate"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:17:08.281Z"}