{"record":{"id":"40a69bc74dc66697","repo":"golang/go","slug":"bufio-scanner-splitfunc-returns-negative-advance","errorCode":null,"errorMessage":"bufio.Scanner: SplitFunc returns negative advance count","messagePattern":"bufio\\.Scanner: SplitFunc returns negative advance count","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/bufio/scan.go","lineNumber":72,"sourceCode":"//\n// Otherwise, the [Scanner] advances the input. If the token is not nil,\n// the [Scanner] returns it to the user. If the token is nil, the\n// Scanner reads more data and continues scanning; if there is no more\n// data--if atEOF was true--the [Scanner] returns. If the data does not\n// yet hold a complete token, for instance if it has no newline while\n// scanning lines, a [SplitFunc] can return (0, nil, nil) to signal the\n// [Scanner] to read more data into the slice and try again with a\n// longer slice starting at the same point in the input.\n//\n// The function is never called with an empty data slice unless atEOF\n// is true. If atEOF is true, however, data may be non-empty and,\n// as always, holds unprocessed text.\ntype SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)\n\n// Errors returned by Scanner.\nvar (\n\tErrTooLong         = errors.New(\"bufio.Scanner: token too long\")\n\tErrNegativeAdvance = errors.New(\"bufio.Scanner: SplitFunc returns negative advance count\")\n\tErrAdvanceTooFar   = errors.New(\"bufio.Scanner: SplitFunc returns advance count beyond input\")\n\tErrBadReadCount    = errors.New(\"bufio.Scanner: Read returned impossible count\")\n)\n\nconst (\n\t// MaxScanTokenSize is the maximum size used to buffer a token\n\t// unless the user provides an explicit buffer with [Scanner.Buffer].\n\t// The actual maximum token size may be smaller as the buffer\n\t// may need to include, for instance, a newline.\n\tMaxScanTokenSize = 64 * 1024\n\n\tstartBufSize = 4096 // Size of initial allocation for buffer.\n)\n\n// NewScanner returns a new [Scanner] to read from r.\n// The split function defaults to [ScanLines].\nfunc NewScanner(r io.Reader) *Scanner {\n\treturn &Scanner{","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/bufio/scan.go#L54-L90","documentation":"bufio.Scanner returns ErrNegativeAdvance when a custom SplitFunc returns an advance value less than zero (scan.go:243-246 via Scanner.advance). The SplitFunc contract requires 0 <= advance <= len(data); a negative advance would move the scan pointer backwards, so the Scanner rejects it and stops. This signals a bug in the user-supplied split function, never in the built-in ScanLines/ScanWords/etc.","triggerScenarios":"Triggered only when a custom SplitFunc passed to Scanner.Split returns (advance<0, token, err). Reachable on every Scan() call that invokes the split function. The built-in split functions (ScanLines, ScanWords, ScanRunes, ScanBytes) never produce this.","commonSituations":"Hand-written SplitFunc that computes advance from an index subtraction that can go negative (e.g., advance = strings.Index(...) - base where the match precedes base). Porting a tokenizer from another language where negative advances were tolerated. Off-by-one in delimiter accounting.","solutions":["Audit the custom SplitFunc: ensure the returned advance is always >= 0 and <= len(data).","Return (0, nil, nil) when you cannot make progress, instead of a negative advance, to ask the Scanner for more data.","Add a property test feeding random inputs and asserting the SplitFunc never returns a negative advance.","If you do not need custom splitting, remove the Scanner.Split call and use a built-in split function."],"exampleFix":"// before — advance can go negative\nsplit := func(data []byte, atEOF bool) (int, []byte, error) {\n    i := bytes.IndexByte(data, '\\t')\n    return i - 1, data[:i], nil // negative if i == 0\n}\n\n// after — guard the advance\nsplit := func(data []byte, atEOF bool) (int, []byte, error) {\n    i := bytes.IndexByte(data, '\\t')\n    if i < 0 {\n        if !atEOF { return 0, nil, nil }\n        return len(data), data, nil\n    }\n    return i + 1, data[:i], nil\n}","handlingStrategy":"validation","validationCode":"// Validate a custom SplitFunc's return before handing it to the Scanner.\nfunc validateSplit(split bufio.SplitFunc) bufio.SplitFunc {\n    return func(data []byte, atEOF bool) (int, []byte, error) {\n        adv, tok, err := split(data, atEOF)\n        if adv < 0 {\n            return 0, nil, fmt.Errorf(\"split returned negative advance %d\", adv)\n        }\n        if adv > len(data) {\n            return 0, nil, fmt.Errorf(\"split returned advance %d > len(data) %d\", adv, len(data))\n        }\n        return adv, tok, err\n    }\n}\n// sc.Split(validateSplit(mySplit))","typeGuard":"// Property: a well-formed SplitFunc never returns negative advance.\nfunc splitIsSafe(split bufio.SplitFunc) bool {\n    // exercise with sample inputs and confirm invariants; omitted for brevity\n    return true\n}","tryCatchPattern":"for sc.Scan() {\n    process(sc.Bytes())\n}\nif err := sc.Err(); err != nil {\n    if errors.Is(err, bufio.ErrNegativeAdvance) {\n        // the custom SplitFunc has a bug; audit it\n    }\n}","preventionTips":["Return (0, nil, nil) from a SplitFunc to request more data, never a negative advance.","Fuzz-test custom SplitFuncs with random byte inputs.","Prefer built-in split functions unless custom logic is essential."],"tags":["go","bufio","scanner","splitfunc","validation"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}