Skip to content

_inputPtr off-by-one in UTF8StreamJsonParser._parseNumber2() #516

@gsson

Description

@gsson

When an input contains space separated root level values, and the input is split such that _parseNumber2() is invoked, _inputPtr will become incremented once in the method

        // As per #105, need separating space between root values; check here
        if (_parsingContext.inRoot()) {
            _verifyRootSpace(_inputBuffer[_inputPtr++] & 0xFF);
        }

and once in _verifyRootSpace():

        // caller had pushed it back, before calling; reset
        ++_inputPtr;

causing the next token to lose its first character.

Related to #105.

Failing test case:

package test;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.stream.Stream;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class JacksonIssue {
    @Test
    public void failingRootLevelParsing() throws IOException {
        // InputStream that forces _parseNumber2 to be invoked.
        InputStream is = TestInputStream.fromStrings("1234", "5 true");

        JsonFactory jsonFactory = new JsonFactory();
        JsonParser parser = jsonFactory.createParser(is);

        // Works!
        assertEquals(12345, parser.nextIntValue(0));

        // Fails with com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'rue': was expecting ('true', 'false' or 'null')
        assertTrue(parser.nextBooleanValue());

    }

    static class TestInputStream extends InputStream {
        private final byte[][] reads;
        private int currentRead;

        public static TestInputStream fromStrings(String ... strings) {
            byte[][] reads = Stream.of(strings)
                    .map(s -> s.getBytes(UTF_8))
                    .toArray(byte[][]::new);
            return new TestInputStream(reads);
        }

        public TestInputStream(byte[][] reads) {
            this.reads = reads;
            this.currentRead = 0;
        }

        @Override
        public int read() throws IOException {
            throw new UnsupportedOperationException();
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            if (currentRead >= reads.length) {
                return -1;
            }
            byte[] bytes = reads[currentRead++];
            if (len < bytes.length) {
                throw new IllegalArgumentException();
            }
            System.arraycopy(bytes, 0, b, off, bytes.length);
            return bytes.length;
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions