{"record":{"id":"1a3219ed7b39da5a","repo":"jhy/jsoup","slug":"wtf-no-buffer-left-to-unconsume","errorCode":null,"errorMessage":"WTF: No buffer left to unconsume.","messagePattern":"WTF: No buffer left to unconsume\\.","errorType":"exception","errorClass":"UncheckedIOException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/jsoup/parser/CharacterReader.java","lineNumber":286,"sourceCode":"    }\n\n    /**\n     Consume one character off the queue.\n     @return first character on queue, or EOF if the queue is empty.\n     */\n    public char consume() {\n        bufferUp();\n        char val = isEmptyNoBufferUp() ? EOF : charBuf[bufPos];\n        bufPos++;\n        return val;\n    }\n\n    /**\n     Unconsume one character (bufPos--). MUST only be called directly after a consume(), and no chance of a bufferUp.\n     */\n    void unconsume() {\n        if (bufPos < 1)\n            throw new UncheckedIOException(new IOException(\"WTF: No buffer left to unconsume.\")); // a bug if this fires, need to trace it.\n\n        bufPos--;\n    }\n\n    /**\n     * Moves the current position by one.\n     */\n    public void advance() {\n        bufPos++;\n    }\n\n    /**\n     * Returns the number of characters between the current position and the next instance of the input char\n     * @param c scan target\n     * @return offset between current position and next instance of target. -1 if not found.\n     */\n    int nextIndexOf(char c) {\n        // doesn't handle scanning for surrogates","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/jhy/jsoup/blob/9851ac5d9c576c6888910b5a51a2362bbc978959/src/main/java/org/jsoup/parser/CharacterReader.java#L268-L304","documentation":"Internal invariant failure in CharacterReader.unconsume: it tries to move bufPos below 0, meaning more characters were unconsumed than consumed. Per its contract, unconsume() is only valid immediately after a consume() with no intervening bufferUp(); the message signals a parser bug rather than bad user input. It is reached when a caller (e.g. recursive unconsume chains or lookbehind helpers like matchesDigit) over-rewinds the buffer.","triggerScenarios":"Thrown at src/main/java/org/jsoup/parser/CharacterReader.java:286 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Ensure unconsume() is called only directly after a consume(), with no bufferUp() or other reads in between.","Fix lookbehind logic to track how many characters were actually consumed before rewinding; never call unconsume() more times than consume().","Replace fragile consume/unconsume pairs with reader.match(...) lookahead APIs, which do not mutate position on failure."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"9851ac5d9c576c6888910b5a51a2362bbc978959","analyzedAt":"2026-09-08T15:22:04.931Z","contentChangedAt":"2026-09-08T15:22:04.931Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}