Version in base suite: 2.14.1-1 Base version: jackson-core_2.14.1-1 Target version: jackson-core_2.14.1-2~deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/j/jackson-core/jackson-core_2.14.1-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/j/jackson-core/jackson-core_2.14.1-2~deb13u1.dsc changelog | 22 control | 2 patches/CVE-2025-52999.patch | 5997 +++++++++++++++++++++++++++++++++++++++++++ patches/series | 1 4 files changed, 6021 insertions(+), 1 deletion(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp22wpq_36/jackson-core_2.14.1-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp22wpq_36/jackson-core_2.14.1-2~deb13u1.dsc: no acceptable signature found diff -Nru jackson-core-2.14.1/debian/changelog jackson-core-2.14.1/debian/changelog --- jackson-core-2.14.1/debian/changelog 2022-12-19 18:42:57.000000000 +0000 +++ jackson-core-2.14.1/debian/changelog 2026-06-07 17:18:04.000000000 +0000 @@ -1,3 +1,25 @@ +jackson-core (2.14.1-2~deb13u1) trixie-security; urgency=medium + + * Team upload. + * Backport 2.14.1 to trixie. + * Fix CVE-2025-52999 and CVE-2025-49128. + + -- Markus Koschany Sun, 07 Jun 2026 19:18:04 +0200 + +jackson-core (2.14.1-2) unstable; urgency=medium + + * Team upload. + + [ Markus Koschany and Emmanuel Arias ] + * CVE-2025-52999: + A limit was added to avoid a StackoverflowError if the parsed JSON is very + deeply nested. The StackoverflowError issue happens in jackson-databind but + this change in jackson-core stops it from happening unless you increase the + StreamReadConstraints.maxNestingDepth() to a high number. (Closes: #1108367). + * Declare compliance with Debian Policy 4.7.4. + + -- Markus Koschany Wed, 15 Apr 2026 13:27:34 +0200 + jackson-core (2.14.1-1) unstable; urgency=medium * Team upload. diff -Nru jackson-core-2.14.1/debian/control jackson-core-2.14.1/debian/control --- jackson-core-2.14.1/debian/control 2022-12-19 18:42:53.000000000 +0000 +++ jackson-core-2.14.1/debian/control 2026-06-07 17:18:04.000000000 +0000 @@ -16,7 +16,7 @@ libmaven-bundle-plugin-java, libmaven-enforcer-plugin-java, libreplacer-java -Standards-Version: 4.6.2 +Standards-Version: 4.7.4 Rules-Requires-Root: no Vcs-Git: https://salsa.debian.org/java-team/jackson-core.git Vcs-Browser: https://salsa.debian.org/java-team/jackson-core diff -Nru jackson-core-2.14.1/debian/patches/CVE-2025-52999.patch jackson-core-2.14.1/debian/patches/CVE-2025-52999.patch --- jackson-core-2.14.1/debian/patches/CVE-2025-52999.patch 1970-01-01 00:00:00.000000000 +0000 +++ jackson-core-2.14.1/debian/patches/CVE-2025-52999.patch 2026-06-07 17:18:04.000000000 +0000 @@ -0,0 +1,5997 @@ +From: Markus Koschany +Date: Wed, 15 Apr 2026 17:03:23 +0200 +Subject: CVE-2025-52999 + +Origin: https://github.com/FasterXML/jackson-core/pull/943 +Bug-Debian: https://bugs.debian.org/1108367 +--- + .../com/fasterxml/jackson/core/JsonFactory.java | 119 ++++--- + .../com/fasterxml/jackson/core/JsonParser.java | 173 ++++++---- + .../fasterxml/jackson/core/JsonStreamContext.java | 14 + + .../jackson/core/StreamReadConstraints.java | 357 +++++++++++++++++++ + .../fasterxml/jackson/core/StreamReadFeature.java | 2 +- + .../com/fasterxml/jackson/core/TSFBuilder.java | 28 +- + .../fasterxml/jackson/core/TokenStreamFactory.java | 24 +- + .../fasterxml/jackson/core/base/ParserBase.java | 377 ++++++++++++++------- + .../jackson/core/base/ParserMinimalBase.java | 10 + + .../core/exc/StreamConstraintsException.java | 25 ++ + .../com/fasterxml/jackson/core/io/IOContext.java | 49 ++- + .../jackson/core/io/JsonStringEncoder.java | 53 ++- + .../jackson/core/io/SegmentedStringWriter.java | 40 ++- + .../jackson/core/json/JsonReadContext.java | 1 + + .../jackson/core/json/ReaderBasedJsonParser.java | 59 ++-- + .../jackson/core/json/UTF8DataInputJsonParser.java | 114 ++++--- + .../jackson/core/json/UTF8StreamJsonParser.java | 142 ++++---- + .../core/json/async/NonBlockingJsonParserBase.java | 52 +-- + .../jackson/core/util/JsonParserDelegate.java | 43 ++- + .../core/util/ReadConstrainedTextBuffer.java | 29 ++ + .../fasterxml/jackson/core/util/TextBuffer.java | 314 +++++++++++++---- + .../jackson/core/fuzz/Fuzz34435ParseTest.java | 1 + + .../core/jsonptr/JsonPointerOOME736Test.java | 5 +- + .../jackson/core/read/ArrayParsingTest.java | 31 +- + .../jackson/core/read/NumberOverflowTest.java | 19 +- + .../jackson/core/read/NumberParsingTest.java | 285 ++++++++-------- + .../fasterxml/jackson/core/util/TestDelegates.java | 33 +- + .../jackson/core/util/TestTextBuffer.java | 45 +-- + 28 files changed, 1745 insertions(+), 699 deletions(-) + create mode 100644 src/main/java/com/fasterxml/jackson/core/StreamReadConstraints.java + create mode 100644 src/main/java/com/fasterxml/jackson/core/exc/StreamConstraintsException.java + create mode 100644 src/main/java/com/fasterxml/jackson/core/util/ReadConstrainedTextBuffer.java + +diff --git a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java +index e291e46..4af1b83 100644 +--- a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java ++++ b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java +@@ -102,7 +102,7 @@ public class JsonFactory + * measure. + *

+ * This setting is enabled by default. +- * ++ * + * @since 2.4 + */ + FAIL_ON_SYMBOL_HASH_OVERFLOW(true), +@@ -144,7 +144,7 @@ public class JsonFactory + } + return flags; + } +- ++ + private Feature(boolean defaultState) { _defaultState = defaultState; } + + @Override +@@ -160,13 +160,13 @@ public class JsonFactory + /* Constants + /********************************************************** + */ +- ++ + /** + * Name used to identify JSON format + * (and returned by {@link #getFormatName()} + */ + public final static String FORMAT_NAME_JSON = "JSON"; +- ++ + /** + * Bitfield (set of flags) of all factory features that are enabled by default. + */ +@@ -177,7 +177,7 @@ public class JsonFactory + * by default. + */ + protected final static int DEFAULT_PARSER_FEATURE_FLAGS = JsonParser.Feature.collectDefaults(); +- ++ + /** + * Bitfield (set of flags) of all generator features that are enabled + * by default. +@@ -225,7 +225,7 @@ public class JsonFactory + * Currently enabled factory features. + */ + protected int _factoryFeatures = DEFAULT_FACTORY_FEATURE_FLAGS; +- ++ + /** + * Currently enabled parser features. + */ +@@ -258,6 +258,14 @@ public class JsonFactory + */ + protected CharacterEscapes _characterEscapes; + ++ /** ++ * Read constraints to use for {@link JsonParser}s constructed using ++ * this factory. ++ * ++ * @since 2.15 ++ */ ++ final protected StreamReadConstraints _streamReadConstraints; ++ + /** + * Optional helper object that may decorate input sources, to do + * additional processing on input during parsing. +@@ -274,7 +282,7 @@ public class JsonFactory + * Separator used between root-level values, if any; null indicates + * "do not add separator". + * Default separator is a single space character. +- * ++ * + * @since 2.1 + */ + protected SerializableString _rootValueSeparator = DEFAULT_ROOT_VALUE_SEPARATOR; +@@ -301,7 +309,7 @@ public class JsonFactory + /* Construction + /********************************************************** + */ +- ++ + /** + * Default constructor used to create factory instances. + * Creation of a factory instance is a light-weight operation, +@@ -317,6 +325,7 @@ public class JsonFactory + public JsonFactory(ObjectCodec oc) { + _objectCodec = oc; + _quoteChar = DEFAULT_QUOTE_CHAR; ++ _streamReadConstraints = StreamReadConstraints.defaults(); + } + + /** +@@ -337,6 +346,7 @@ public class JsonFactory + _generatorFeatures = src._generatorFeatures; + _inputDecorator = src._inputDecorator; + _outputDecorator = src._outputDecorator; ++ _streamReadConstraints = src._streamReadConstraints; + + // JSON-specific + _characterEscapes = src._characterEscapes; +@@ -361,6 +371,7 @@ public class JsonFactory + _generatorFeatures = b._streamWriteFeatures; + _inputDecorator = b._inputDecorator; + _outputDecorator = b._outputDecorator; ++ _streamReadConstraints = b._streamReadConstraints; + + // JSON-specific + _characterEscapes = b._characterEscapes; +@@ -373,7 +384,7 @@ public class JsonFactory + * Constructor for subtypes; needed to work around the fact that before 3.0, + * this factory has cumbersome dual role as generic type as well as actual + * implementation for json. +- * ++ * + * @param b Builder that contains settings to use + * @param bogus Argument only needed to separate constructor signature; ignored + */ +@@ -385,6 +396,7 @@ public class JsonFactory + _generatorFeatures = b._streamWriteFeatures; + _inputDecorator = b._inputDecorator; + _outputDecorator = b._outputDecorator; ++ _streamReadConstraints = b._streamReadConstraints; + + // JSON-specific: need to assign even if not really used + _characterEscapes = null; +@@ -529,7 +541,7 @@ public class JsonFactory + * + * @return Whether access to decoded textual content can be efficiently + * accessed using parser method {@code getTextCharacters()}. +- * ++ * + * @since 2.4 + */ + public boolean canUseCharArrays() { return true; } +@@ -627,11 +639,11 @@ public class JsonFactory + * using {@link JsonParser} constructed by this factory + * (which typically also implies the same for serialization + * with {@link JsonGenerator}). +- * ++ * + * @return True if custom codec is needed with parsers and + * generators created by this factory; false if a general + * {@link ObjectCodec} is enough +- * ++ * + * @since 2.1 + */ + public boolean requiresCustomCodec() { +@@ -740,6 +752,17 @@ public class JsonFactory + return 0; + } + ++ /* ++ /********************************************************************** ++ /* Constraints violation checking (2.15) ++ /********************************************************************** ++ */ ++ ++ @Override ++ public StreamReadConstraints streamReadConstraints() { ++ return _streamReadConstraints; ++ } ++ + /* + /********************************************************** + /* Configuration, parser configuration +@@ -826,7 +849,7 @@ public class JsonFactory + * @param d Decorator to configure for this factory, if any ({@code null} if none) + * + * @return This factory instance (to allow call chaining) +- * ++ * + * @deprecated Since 2.10 use {@link JsonFactoryBuilder#inputDecorator(InputDecorator)} instead + */ + @Deprecated +@@ -857,7 +880,7 @@ public class JsonFactory + /** + * Method for enabling specified generator features + * (check {@link JsonGenerator.Feature} for list of features) +- * ++ * + * @param f Feature to enable + * + * @return This factory instance (to allow call chaining) +@@ -904,7 +927,7 @@ public class JsonFactory + public final boolean isEnabled(StreamWriteFeature f) { + return (_generatorFeatures & f.mappedFeature().getMask()) != 0; + } +- ++ + /** + * Method for accessing custom escapes factory uses for {@link JsonGenerator}s + * it creates. +@@ -955,7 +978,7 @@ public class JsonFactory + /** + * Method that allows overriding String used for separating root-level + * JSON values (default is single space character) +- * ++ * + * @param sep Separator to use, if any; null means that no separator is + * automatically added + * +@@ -1020,7 +1043,7 @@ public class JsonFactory + * the parser, since caller has no access to it. + * + * @param f File that contains JSON content to parse +- * ++ * + * @since 2.1 + */ + @Override +@@ -1046,7 +1069,7 @@ public class JsonFactory + * the parser, since caller has no access to it. + * + * @param url URL pointing to resource that contains JSON content to parse +- * ++ * + * @since 2.1 + */ + @Override +@@ -1075,7 +1098,7 @@ public class JsonFactory + * For other charsets use {@link #createParser(java.io.Reader)}. + * + * @param in InputStream to use for reading JSON content to parse +- * ++ * + * @since 2.1 + */ + @Override +@@ -1083,7 +1106,7 @@ public class JsonFactory + IOContext ctxt = _createContext(_createContentReference(in), false); + return _createParser(_decorate(in, ctxt), ctxt); + } +- ++ + /** + * Method for constructing parser for parsing + * the contents accessed via specified Reader. +@@ -1095,7 +1118,7 @@ public class JsonFactory + * is enabled. + * + * @param r Reader to use for reading JSON content to parse +- * ++ * + * @since 2.1 + */ + @Override +@@ -1108,7 +1131,7 @@ public class JsonFactory + /** + * Method for constructing parser for parsing + * the contents of given byte array. +- * ++ * + * @since 2.1 + */ + @Override +@@ -1126,11 +1149,11 @@ public class JsonFactory + /** + * Method for constructing parser for parsing + * the contents of given byte array. +- * ++ * + * @param data Buffer that contains data to parse + * @param offset Offset of the first data byte within buffer + * @param len Length of contents to parse within buffer +- * ++ * + * @since 2.1 + */ + @Override +@@ -1150,7 +1173,7 @@ public class JsonFactory + /** + * Method for constructing parser for parsing + * contents of given String. +- * ++ * + * @since 2.1 + */ + @Override +@@ -1171,17 +1194,17 @@ public class JsonFactory + /** + * Method for constructing parser for parsing + * contents of given char array. +- * ++ * + * @since 2.4 + */ + @Override + public JsonParser createParser(char[] content) throws IOException { + return createParser(content, 0, content.length); + } +- ++ + /** + * Method for constructing parser for parsing contents of given char array. +- * ++ * + * @since 2.4 + */ + @Override +@@ -1294,14 +1317,14 @@ public class JsonFactory + * Note: there are formats that use fixed encoding (like most binary data formats) + * and that ignore passed in encoding. + * +- * @param out OutputStream to use for writing JSON content ++ * @param out OutputStream to use for writing JSON content + * @param enc Character encoding to use +- * ++ * + * @since 2.1 + */ + @Override + public JsonGenerator createGenerator(OutputStream out, JsonEncoding enc) +- throws IOException ++ throws IOException + { + // false -> we won't manage the stream unless explicitly directed to + IOContext ctxt = _createContext(_createContentReference(out), false); +@@ -1318,14 +1341,14 @@ public class JsonFactory + * encoding of the format (UTF-8 for JSON and most other data formats). + *

+ * Note: there are formats that use fixed encoding (like most binary data formats). +- * ++ * + * @since 2.1 + */ + @Override + public JsonGenerator createGenerator(OutputStream out) throws IOException { + return createGenerator(out, JsonEncoding.UTF8); + } +- ++ + /** + * Method for constructing JSON generator for writing JSON content + * using specified Writer. +@@ -1336,17 +1359,17 @@ public class JsonFactory + * feature, + * {@link com.fasterxml.jackson.core.JsonGenerator.Feature#AUTO_CLOSE_TARGET} is enabled). + * Using application needs to close it explicitly. +- * ++ * + * @since 2.1 + * +- * @param w Writer to use for writing JSON content ++ * @param w Writer to use for writing JSON content + */ + @Override + public JsonGenerator createGenerator(Writer w) throws IOException { + IOContext ctxt = _createContext(_createContentReference(w), false); + return _createGenerator(_decorate(w, ctxt), ctxt); + } +- ++ + /** + * Method for constructing JSON generator for writing JSON content + * to specified file, overwriting contents it might have (or creating +@@ -1360,7 +1383,7 @@ public class JsonFactory + * + * @param f File to write contents to + * @param enc Character encoding to use +- * ++ * + * @since 2.1 + */ + @Override +@@ -1375,12 +1398,12 @@ public class JsonFactory + } + Writer w = _createWriter(out, enc, ctxt); + return _createGenerator(_decorate(w, ctxt), ctxt); +- } ++ } + + /** + * Method for constructing generator for writing content using specified + * {@link DataOutput} instance. +- * ++ * + * @since 2.8 + */ + @Override +@@ -1393,7 +1416,7 @@ public class JsonFactory + * encoding of the format (UTF-8 for JSON and most other data formats). + *

+ * Note: there are formats that use fixed encoding (like most binary data formats). +- * ++ * + * @since 2.8 + */ + @Override +@@ -1425,7 +1448,7 @@ public class JsonFactory + * @param f File that contains JSON content to parse + * + * @return Parser constructed +- * ++ * + * @throws IOException if parser initialization fails due to I/O (read) problem + * @throws JsonParseException if parser initialization fails due to content decoding problem + * +@@ -1539,13 +1562,13 @@ public class JsonFactory + /** + * Method for constructing parser for parsing + * the contents of given byte array. +- * ++ * + * @param data Buffer that contains data to parse + * @param offset Offset of the first data byte within buffer + * @param len Length of contents to parse within buffer + * + * @return Parser constructed +- * ++ * + * @throws IOException if parser initialization fails due to I/O (read) problem + * @throws JsonParseException if parser initialization fails due to content decoding problem + * +@@ -1597,7 +1620,7 @@ public class JsonFactory + * Note: there are formats that use fixed encoding (like most binary data formats) + * and that ignore passed in encoding. + * +- * @param out OutputStream to use for writing JSON content ++ * @param out OutputStream to use for writing JSON content + * @param enc Character encoding to use + * + * @return Generator constructed +@@ -1622,7 +1645,7 @@ public class JsonFactory + * {@link com.fasterxml.jackson.core.JsonGenerator.Feature#AUTO_CLOSE_TARGET} is enabled). + * Using application needs to close it explicitly. + * +- * @param out Writer to use for writing JSON content ++ * @param out Writer to use for writing JSON content + * + * @return Generator constructed + * +@@ -1641,7 +1664,7 @@ public class JsonFactory + *

+ * Note: there are formats that use fixed encoding (like most binary data formats). + * +- * @param out OutputStream to use for writing JSON content ++ * @param out OutputStream to use for writing JSON content + * + * @return Generator constructed + * +@@ -1713,7 +1736,7 @@ public class JsonFactory + * @return Actual parser to use + * + * @throws IOException if parser initialization fails due to I/O (read) problem +- * ++ * + * @since 2.1 + */ + protected JsonParser _createParser(Reader r, IOContext ctxt) throws IOException { +@@ -1801,7 +1824,7 @@ public class JsonFactory + /* overridable by sub-classes + /********************************************************** + */ +- ++ + /** + * Overridable factory method that actually instantiates generator for + * given {@link Writer} and context object. +diff --git a/src/main/java/com/fasterxml/jackson/core/JsonParser.java b/src/main/java/com/fasterxml/jackson/core/JsonParser.java +index 50c6b0e..fbaebaf 100644 +--- a/src/main/java/com/fasterxml/jackson/core/JsonParser.java ++++ b/src/main/java/com/fasterxml/jackson/core/JsonParser.java +@@ -169,7 +169,7 @@ public abstract class JsonParser + + /** + * Feature that determines whether parser will allow +- * JSON integral numbers to start with additional (ignorable) ++ * JSON integral numbers to start with additional (ignorable) + * zeroes (like: 000001). If enabled, no exception is thrown, and extra + * nulls are silently ignored (and not included in textual representation + * exposed via {@link JsonParser#getText}). +@@ -232,11 +232,11 @@ public abstract class JsonParser + * in data-binding. + *

+ * For example, enabling this feature will represent a JSON array ["value1",,"value3",] +- * as ["value1", null, "value3", null] ++ * as ["value1", null, "value3", null] + *

+ * Since the JSON specification does not allow missing values this is a non-compliant JSON + * feature and is disabled by default. +- * ++ * + * @since 2.8 + * + * @deprecated Since 2.10 use {@link com.fasterxml.jackson.core.json.JsonReadFeature#ALLOW_MISSING_VALUES} instead +@@ -271,7 +271,7 @@ public abstract class JsonParser + ALLOW_TRAILING_COMMA(false), + + // // // Validity checks +- ++ + /** + * Feature that determines whether {@link JsonParser} will explicitly + * check that no duplicate JSON Object field names are encountered. +@@ -285,7 +285,7 @@ public abstract class JsonParser + * Note that enabling this feature will incur performance overhead + * due to having to store and check additional information: this typically + * adds 20-30% to execution time for basic parsing. +- * ++ * + * @since 2.3 + */ + STRICT_DUPLICATE_DETECTION(false), +@@ -339,7 +339,7 @@ public abstract class JsonParser + + /** + * Feature that determines whether we use the built-in {@link Double#parseDouble(String)} code to parse +- * doubles or if we use {@link com.fasterxml.jackson.core.io.doubleparser} ++ * doubles or if we use {@code FastDoubleParser} implementation. + * instead. + *

+ * This setting is disabled by default for backwards compatibility. +@@ -348,6 +348,17 @@ public abstract class JsonParser + */ + USE_FAST_DOUBLE_PARSER(false), + ++ /** ++ * Feature that determines whether to use the built-in Java code for parsing ++ * BigDecimals and BigIntegerss or to use ++ * specifically optimized custom implementation instead. ++ *

++ * This setting is disabled by default for backwards compatibility. ++ * ++ * @since 2.15 ++ */ ++ USE_FAST_BIG_NUMBER_PARSER(false) ++ + ; + + /** +@@ -356,7 +367,7 @@ public abstract class JsonParser + private final boolean _defaultState; + + private final int _mask; +- ++ + /** + * Method that calculates bit set (flags) of all features that + * are enabled by default. +@@ -398,7 +409,7 @@ public abstract class JsonParser + * are enabled. + */ + protected int _features; +- ++ + /** + * Optional container that holds the request payload which will be displayed on JSON parsing error. + * +@@ -466,7 +477,7 @@ public abstract class JsonParser + public void setRequestPayloadOnError(RequestPayload payload) { + _requestPayload = payload; + } +- ++ + /** + * Sets the byte[] request payload and the charset + * +@@ -490,6 +501,21 @@ public abstract class JsonParser + _requestPayload = (payload == null) ? null : new RequestPayload(payload); + } + ++ /* ++ /********************************************************************** ++ /* Constraints violation checking (2.15) ++ /********************************************************************** ++ */ ++ ++ /** ++ * Get the constraints to apply when performing streaming reads. ++ * ++ * @since 2.15 ++ */ ++ public StreamReadConstraints streamReadConstraints() { ++ return StreamReadConstraints.defaults(); ++ } ++ + /* + /********************************************************** + /* Format support +@@ -505,9 +531,9 @@ public abstract class JsonParser + *

+ * If parser does not support specified schema, {@link UnsupportedOperationException} + * is thrown. +- * ++ * + * @param schema Schema to use +- * ++ * + * @throws UnsupportedOperationException if parser does not support schema + */ + public void setSchema(FormatSchema schema) { +@@ -524,13 +550,13 @@ public abstract class JsonParser + * @since 2.1 + */ + public FormatSchema getSchema() { return null; } +- ++ + /** + * Method that can be used to verify that given schema can be used with + * this parser (using {@link #setSchema}). +- * ++ * + * @param schema Schema to check +- * ++ * + * @return True if this parser can use given schema; false if not + */ + public boolean canUseSchema(FormatSchema schema) { return false; } +@@ -540,17 +566,17 @@ public abstract class JsonParser + /* Capability introspection + /********************************************************** + */ +- ++ + /** + * Method that can be called to determine if a custom + * {@link ObjectCodec} is needed for binding data parsed + * using {@link JsonParser} constructed by this factory + * (which typically also implies the same for serialization + * with {@link JsonGenerator}). +- * ++ * + * @return True if format-specific codec is needed with this parser; false if a general + * {@link ObjectCodec} is enough +- * ++ * + * @since 2.1 + */ + public boolean requiresCustomCodec() { return false;} +@@ -564,8 +590,8 @@ public abstract class JsonParser + * If non-blocking decoding is (@code true}, it is possible to call + * {@link #getNonBlockingInputFeeder()} to obtain object to use + * for feeding input; otherwise (false returned) +- * input is read by blocking +- * ++ * input is read by blocking ++ * + * @return True if this is a non-blocking ("asynchronous") parser + * + * @since 2.9 +@@ -832,7 +858,7 @@ public abstract class JsonParser + * otherwise number of chars released (0 if there was nothing to release) + * + * @throws IOException if write using Writer threw exception +- */ ++ */ + public int releaseBuffered(Writer w) throws IOException { return -1; } + + /* +@@ -880,7 +906,7 @@ public abstract class JsonParser + if (state) enable(f); else disable(f); + return this; + } +- ++ + /** + * Method for checking whether specified {@link Feature} is enabled. + * +@@ -900,10 +926,10 @@ public abstract class JsonParser + * @since 2.10 + */ + public boolean isEnabled(StreamReadFeature f) { return f.mappedFeature().enabledIn(_features); } +- ++ + /** + * Bulk access method for getting state of all standard {@link Feature}s. +- * ++ * + * @return Bit mask that defines current states of all standard {@link Feature}s. + * + * @since 2.3 +@@ -935,7 +961,7 @@ public abstract class JsonParser + * setFeatureMask(newState); + * + * but preferred as this lets caller more efficiently specify actual changes made. +- * ++ * + * @param values Bit mask of set/clear state for features to change + * @param mask Bit mask of features to change + * +@@ -951,15 +977,15 @@ public abstract class JsonParser + /** + * Bulk access method for getting state of all {@link FormatFeature}s, format-specific + * on/off configuration settings. +- * ++ * + * @return Bit mask that defines current states of all standard {@link FormatFeature}s. +- * ++ * + * @since 2.6 + */ + public int getFormatFeatures() { + return 0; + } +- ++ + /** + * Bulk set method for (re)setting states of {@link FormatFeature}s, + * by specifying values (set / clear) along with a mask, to determine +@@ -972,7 +998,7 @@ public abstract class JsonParser + * @param mask Bit mask of features to change + * + * @return This parser, to allow call chaining +- * ++ * + * @since 2.6 + */ + public JsonParser overrideFormatFeatures(int values, int mask) { +@@ -1034,7 +1060,7 @@ public abstract class JsonParser + * + * but may be faster for parser to verify, and can therefore be used if caller + * expects to get such a property name from input next. +- * ++ * + * @param str Property name to compare next token to (if next token is + * JsonToken.FIELD_NAME) + * +@@ -1168,7 +1194,7 @@ public abstract class JsonParser + /** + * Method that will skip all child tokens of an array or + * object token that the parser currently points to, +- * iff stream points to ++ * iff stream points to + * {@link JsonToken#START_OBJECT} or {@link JsonToken#START_ARRAY}. + * If not, it will do nothing. + * After skipping, stream will point to matching +@@ -1240,7 +1266,7 @@ public abstract class JsonParser + * to profile performance before deciding to use this method. + * + * @since 2.8 +- * ++ * + * @return {@code int} matching one of constants from {@link JsonTokenId}. + */ + public int currentTokenId() { +@@ -1365,7 +1391,7 @@ public abstract class JsonParser + * @since 2.12 + */ + public boolean isExpectedNumberIntToken() { return currentToken() == JsonToken.VALUE_NUMBER_INT; } +- ++ + /** + * Access for checking whether current token is a numeric value token, but + * one that is of "not-a-number" (NaN) variety (including both "NaN" AND +@@ -1417,7 +1443,7 @@ public abstract class JsonParser + * @return Last cleared token, if any; {@code null} otherwise + */ + public abstract JsonToken getLastClearedToken(); +- ++ + /** + * Method that can be used to change what is considered to be + * the current (field) name. +@@ -1426,7 +1452,7 @@ public abstract class JsonParser + *

+ * Note that use of this method should only be done as sort of last + * resort, as it is a work-around for regular operation. +- * ++ * + * @param name Name to use as the current name; may be null. + */ + public abstract void overrideCurrentName(String name); +@@ -1476,12 +1502,13 @@ public abstract class JsonParser + * by {@link #nextToken()} or other iteration methods) + * + * @throws IOException for low-level read issues, or +- * {@link JsonParseException} for decoding problems ++ * {@link JsonParseException} for decoding problems, including if the text is too large, ++ * see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} + */ + public abstract String getText() throws IOException; + + /** +- * Method to read the textual representation of the current token in chunks and ++ * Method to read the textual representation of the current token in chunks and + * pass it to the given Writer. + * Conceptually same as calling: + *

+@@ -1540,7 +1567,8 @@ public abstract class JsonParser
+      *    at offset 0, and not necessarily until the end of buffer)
+      *
+      * @throws IOException for low-level read issues, or
+-     *   {@link JsonParseException} for decoding problems
++     *   {@link JsonParseException} for decoding problems, including if the text is too large,
++     *   see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)}
+      */
+     public abstract char[] getTextCharacters() throws IOException;
+ 
+@@ -1575,14 +1603,14 @@ public abstract class JsonParser
+      * {@link #getTextCharacters} would be the most efficient
+      * way to access textual content for the event parser currently
+      * points to.
+-     *

++ *

+ * Default implementation simply returns false since only actual + * implementation class has knowledge of its internal buffering + * state. + * Implementations are strongly encouraged to properly override + * this method, to allow efficient copying of content by other + * code. +- * ++ * + * @return True if parser currently has character array that can + * be efficiently returned via {@link #getTextCharacters}; false + * means that it may or may not exist +@@ -1635,7 +1663,38 @@ public abstract class JsonParser + } + + /** +- * If current token is of type ++ * Method similar to {@link #getNumberValue} but that returns ++ * either same {@link Number} value as {@link #getNumberValue()} ++ * (if already decoded), or {@code String} representation of ++ * as-of-yet undecoded number. ++ * Typically textual formats allow deferred decoding from String, whereas ++ * binary formats either decode numbers eagerly or have binary representation ++ * from which to decode value to return. ++ *

++ * Same constraints apply to calling this method as to {@link #getNumberValue()}: ++ * current token must be either ++ * {@link JsonToken#VALUE_NUMBER_INT} or ++ * {@link JsonToken#VALUE_NUMBER_FLOAT}; ++ * otherwise an exception is thrown ++ *

++ * Default implementation simply returns {@link #getNumberValue()} ++ * ++ * @return Either {@link Number} (for already decoded numbers) or ++ * {@link String} (for deferred decoding). ++ * ++ * @throws IOException Problem with access: {@link JsonParseException} if ++ * the current token is not numeric, or if decoding of the value fails ++ * (invalid format for numbers); plain {@link IOException} if underlying ++ * content read fails (possible if values are extracted lazily) ++ * ++ * @since 2.15 ++ */ ++ public Object getNumberValueDeferred() throws IOException { ++ return getNumberValue(); ++ } ++ ++ /** ++ * If current token is of type + * {@link JsonToken#VALUE_NUMBER_INT} or + * {@link JsonToken#VALUE_NUMBER_FLOAT}, returns + * one of {@link NumberType} constants; otherwise returns null. +@@ -1835,7 +1894,7 @@ public abstract class JsonParser + /* Public API, access to token information, other + /********************************************************** + */ +- ++ + /** + * Convenience accessor that can be called when the current + * token is {@link JsonToken#VALUE_TRUE} or +@@ -1934,9 +1993,9 @@ public abstract class JsonParser + * is that content will NOT remain accessible after method returns: any content + * processed will be consumed and is not buffered in any way. If caller needs + * buffering, it has to implement it. +- * ++ * + * @param out Output stream to use for passing decoded binary data +- * ++ * + * @return Number of bytes that were decoded and written via {@link OutputStream} + * + * @throws IOException for low-level read issues, or +@@ -1951,10 +2010,10 @@ public abstract class JsonParser + /** + * Similar to {@link #readBinaryValue(OutputStream)} but allows explicitly + * specifying base64 variant to use. +- * ++ * + * @param bv base64 variant to use + * @param out Output stream to use for passing decoded binary data +- * ++ * + * @return Number of bytes that were decoded and written via {@link OutputStream} + * + * @throws IOException for low-level read issues, or +@@ -1966,13 +2025,13 @@ public abstract class JsonParser + _reportUnsupportedOperation(); + return 0; // never gets here + } +- ++ + /* + /********************************************************** + /* Public API, access to token information, coercion/conversion + /********************************************************** + */ +- ++ + /** + * Method that will try to convert value of current token to a + * Java {@code int} value. +@@ -1993,7 +2052,7 @@ public abstract class JsonParser + public int getValueAsInt() throws IOException { + return getValueAsInt(0); + } +- ++ + /** + * Method that will try to convert value of current token to a + * int. +@@ -2034,7 +2093,7 @@ public abstract class JsonParser + public long getValueAsLong() throws IOException { + return getValueAsLong(0); + } +- ++ + /** + * Method that will try to convert value of current token to a + * long. +@@ -2056,7 +2115,7 @@ public abstract class JsonParser + public long getValueAsLong(long def) throws IOException { + return def; + } +- ++ + /** + * Method that will try to convert value of current token to a Java + * double. +@@ -2077,7 +2136,7 @@ public abstract class JsonParser + public double getValueAsDouble() throws IOException { + return getValueAsDouble(0.0); + } +- ++ + /** + * Method that will try to convert value of current token to a + * Java double. +@@ -2162,7 +2221,7 @@ public abstract class JsonParser + public String getValueAsString() throws IOException { + return getValueAsString(null); + } +- ++ + /** + * Method that will try to convert value of current token to a + * {@link java.lang.String}. +@@ -2218,7 +2277,7 @@ public abstract class JsonParser + * + * @return {@code True} if the format being read supports native Type Ids; + * {@code false} if not +- * ++ * + * @since 2.3 + */ + public boolean canReadTypeId() { return false; } +@@ -2296,7 +2355,7 @@ public abstract class JsonParser + * deserializes content) + * + * @return Java value read from content +- * ++ * + * @throws IOException if there is either an underlying I/O problem or decoding + * issue at format layer + */ +@@ -2308,7 +2367,7 @@ public abstract class JsonParser + * Method to deserialize JSON content into a Java type, reference + * to which is passed as argument. Type is passed using so-called + * "super type token" +- * and specifically needs to be used if the root type is a ++ * and specifically needs to be used if the root type is a + * parameterized (generic) container type. + * Note: method can only be called if the parser has + * an object codec assigned; this is true for parsers constructed +@@ -2372,7 +2431,7 @@ public abstract class JsonParser + public Iterator readValuesAs(TypeReference valueTypeRef) throws IOException { + return _codec().readValues(this, valueTypeRef); + } +- ++ + /** + * Method to deserialize JSON content into equivalent "tree model", + * represented by root {@link TreeNode} of resulting model. +@@ -2399,7 +2458,7 @@ public abstract class JsonParser + } + return c; + } +- ++ + /* + /********************************************************** + /* Internal methods +diff --git a/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java b/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java +index b6854e6..1868ab5 100644 +--- a/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java ++++ b/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java +@@ -52,6 +52,12 @@ public abstract class JsonStreamContext + */ + protected int _index; + ++ /** ++ * The nesting depth is a count of objects and arrays that have not ++ * been closed, `{` and `[` respectively. ++ */ ++ protected int _nestingDepth; ++ + /* + /********************************************************** + /* Life-cycle +@@ -118,6 +124,14 @@ public abstract class JsonStreamContext + */ + public final boolean inObject() { return _type == TYPE_OBJECT; } + ++ /** ++ * The nesting depth is a count of objects and arrays that have not ++ * been closed, `{` and `[` respectively. ++ */ ++ public final int getNestingDepth() { ++ return _nestingDepth; ++ } ++ + /** + * @return Type description String + * +diff --git a/src/main/java/com/fasterxml/jackson/core/StreamReadConstraints.java b/src/main/java/com/fasterxml/jackson/core/StreamReadConstraints.java +new file mode 100644 +index 0000000..c61793e +--- /dev/null ++++ b/src/main/java/com/fasterxml/jackson/core/StreamReadConstraints.java +@@ -0,0 +1,357 @@ ++package com.fasterxml.jackson.core; ++ ++import com.fasterxml.jackson.core.exc.StreamConstraintsException; ++ ++/** ++ * The constraints to use for streaming reads: used to guard against malicious ++ * input by preventing processing of "too big" input constructs (values, ++ * structures). ++ * Constraints are registered with {@code TokenStreamFactory} (such as ++ * {@code JsonFactory}); if nothing explicitly specified, default ++ * constraints are used. ++ *

++ * Currently constrained aspects, with default settings, are: ++ *

    ++ *
  • Maximum Number value length: default 1000 (see {@link #DEFAULT_MAX_NUM_LEN}) ++ *
  • ++ *
  • Maximum String value length: default 20_000_000 (see {@link #DEFAULT_MAX_STRING_LEN}) ++ *
  • ++ *
  • Maximum Nesting depth: default 1000 (see {@link #DEFAULT_MAX_DEPTH}) ++ *
  • ++ *
++ * ++ * @since 2.15 ++ */ ++public class StreamReadConstraints ++ implements java.io.Serializable ++{ ++ private static final long serialVersionUID = 1L; ++ ++ /** ++ * Default setting for maximum depth: see {@link Builder#maxNestingDepth(int)} for details. ++ */ ++ public static final int DEFAULT_MAX_DEPTH = 1000; ++ ++ /** ++ * Default setting for maximum number length: see {@link Builder#maxNumberLength(int)} for details. ++ */ ++ public static final int DEFAULT_MAX_NUM_LEN = 1000; ++ ++ /** ++ * Default setting for maximum string length: see {@link Builder#maxStringLength(int)} ++ * for details. ++ *

++ * NOTE: Jackson 2.15.0 initially used a lower setting (5_000_000). ++ */ ++ public static final int DEFAULT_MAX_STRING_LEN = 20_000_000; ++ ++ /** ++ * Limit for the maximum magnitude of Scale of {@link java.math.BigDecimal} that can be ++ * converted to {@link java.math.BigInteger}. ++ *

++ * "100k digits ought to be enough for anybody!" ++ */ ++ private static final int MAX_BIGINT_SCALE_MAGNITUDE = 100_000; ++ ++ protected final int _maxNestingDepth; ++ protected final int _maxNumLen; ++ protected final int _maxStringLen; ++ ++ private static StreamReadConstraints DEFAULT = ++ new StreamReadConstraints(DEFAULT_MAX_DEPTH, DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN); ++ ++ /** ++ * Override the default StreamReadConstraints. These defaults are only used when {@link JsonFactory} ++ * instances are not configured with their own StreamReadConstraints. ++ *

++ * Library maintainers should not set this as it will affect other code that uses Jackson. ++ * Library maintainers who want to configure StreamReadConstraints for the Jackson usage within their ++ * lib should create ObjectMapper instances that have a {@link JsonFactory} instance with ++ * the required StreamReadConstraints. ++ *

++ * This method is meant for users delivering applications. If they use this, they set it when they start ++ * their application to avoid having other code initialize their mappers before the defaults are overridden. ++ * ++ * @param streamReadConstraints new default for StreamReadConstraints (a null value will reset to built-in default) ++ * @see #defaults() ++ * @see #builder() ++ * @since v2.15.2 ++ */ ++ public static void overrideDefaultStreamReadConstraints(final StreamReadConstraints streamReadConstraints) { ++ if (streamReadConstraints == null) { ++ DEFAULT = new StreamReadConstraints(DEFAULT_MAX_DEPTH, DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN); ++ } else { ++ DEFAULT = streamReadConstraints; ++ } ++ } ++ ++ public static final class Builder { ++ private int maxNestingDepth; ++ private int maxNumLen; ++ private int maxStringLen; ++ ++ /** ++ * Sets the maximum nesting depth. The depth is a count of objects and arrays that have not ++ * been closed, `{` and `[` respectively. ++ * ++ * @param maxNestingDepth the maximum depth ++ * ++ * @return this builder ++ * @throws IllegalArgumentException if the maxNestingDepth is set to a negative value ++ */ ++ public Builder maxNestingDepth(final int maxNestingDepth) { ++ if (maxNestingDepth < 0) { ++ throw new IllegalArgumentException("Cannot set maxNestingDepth to a negative value"); ++ } ++ this.maxNestingDepth = maxNestingDepth; ++ return this; ++ } ++ ++ /** ++ * Sets the maximum number length (in chars or bytes, depending on input context). ++ * The default is 1000. ++ * ++ * @param maxNumLen the maximum number length (in chars or bytes, depending on input context) ++ * ++ * @return this builder ++ * @throws IllegalArgumentException if the maxNumLen is set to a negative value ++ */ ++ public Builder maxNumberLength(final int maxNumLen) { ++ if (maxNumLen < 0) { ++ throw new IllegalArgumentException("Cannot set maxNumberLength to a negative value"); ++ } ++ this.maxNumLen = maxNumLen; ++ return this; ++ } ++ ++ /** ++ * Sets the maximum string length (in chars or bytes, depending on input context). ++ * The default is 20,000,000. This limit is not exact, the limit is applied when we increase ++ * internal buffer sizes and an exception will happen at sizes greater than this limit. Some ++ * text values that are a little bigger than the limit may be treated as valid but no text ++ * values with sizes less than or equal to this limit will be treated as invalid. ++ *

++ * Setting this value to lower than the {@link #maxNumberLength(int)} is not recommended. ++ *

++ *

++ * NOTE: Jackson 2.15.0 initially used a lower setting (5_000_000). ++ * ++ * @param maxStringLen the maximum string length (in chars or bytes, depending on input context) ++ * ++ * @return this builder ++ * @throws IllegalArgumentException if the maxStringLen is set to a negative value ++ */ ++ public Builder maxStringLength(final int maxStringLen) { ++ if (maxStringLen < 0) { ++ throw new IllegalArgumentException("Cannot set maxStringLen to a negative value"); ++ } ++ this.maxStringLen = maxStringLen; ++ return this; ++ } ++ ++ Builder() { ++ this(DEFAULT_MAX_DEPTH, DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN); ++ } ++ ++ Builder(final int maxNestingDepth, final int maxNumLen, final int maxStringLen) { ++ this.maxNestingDepth = maxNestingDepth; ++ this.maxNumLen = maxNumLen; ++ this.maxStringLen = maxStringLen; ++ } ++ ++ Builder(StreamReadConstraints src) { ++ maxNestingDepth = src._maxNestingDepth; ++ maxNumLen = src._maxNumLen; ++ maxStringLen = src._maxStringLen; ++ } ++ ++ public StreamReadConstraints build() { ++ return new StreamReadConstraints(maxNestingDepth, maxNumLen, maxStringLen); ++ } ++ } ++ ++ /* ++ /********************************************************************** ++ /* Life-cycle ++ /********************************************************************** ++ */ ++ ++ protected StreamReadConstraints(final int maxNestingDepth, final int maxNumLen, final int maxStringLen) { ++ _maxNestingDepth = maxNestingDepth; ++ _maxNumLen = maxNumLen; ++ _maxStringLen = maxStringLen; ++ } ++ ++ public static Builder builder() { ++ return new Builder(); ++ } ++ ++ /** ++ * @return the default {@link StreamReadConstraints} (when none is set on the {@link JsonFactory} explicitly) ++ * @see #overrideDefaultStreamReadConstraints ++ */ ++ public static StreamReadConstraints defaults() { ++ return DEFAULT; ++ } ++ ++ /** ++ * @return New {@link Builder} initialized with settings of this constraints ++ * instance ++ */ ++ public Builder rebuild() { ++ return new Builder(this); ++ } ++ ++ /* ++ /********************************************************************** ++ /* Accessors ++ /********************************************************************** ++ */ ++ ++ /** ++ * Accessor for maximum depth. ++ * see {@link Builder#maxNestingDepth(int)} for details. ++ * ++ * @return Maximum allowed depth ++ */ ++ public int getMaxNestingDepth() { ++ return _maxNestingDepth; ++ } ++ ++ /** ++ * Accessor for maximum length of numbers to decode. ++ * see {@link Builder#maxNumberLength(int)} for details. ++ * ++ * @return Maximum allowed number length ++ */ ++ public int getMaxNumberLength() { ++ return _maxNumLen; ++ } ++ ++ /** ++ * Accessor for maximum length of strings to decode. ++ * see {@link Builder#maxStringLength(int)} for details. ++ * ++ * @return Maximum allowed string length ++ */ ++ public int getMaxStringLength() { ++ return _maxStringLen; ++ } ++ ++ /* ++ /********************************************************************** ++ /* Convenience methods for validation, document limits ++ /********************************************************************** ++ */ ++ ++ /** ++ * Convenience method that can be used to verify that the ++ * nesting depth does not exceed the maximum specified by this ++ * constraints object: if it does, a ++ * {@link StreamConstraintsException} ++ * is thrown. ++ * ++ * @param depth count of unclosed objects and arrays ++ * ++ * @throws StreamConstraintsException If depth exceeds maximum ++ */ ++ public void validateNestingDepth(int depth) throws StreamConstraintsException ++ { ++ if (depth > _maxNestingDepth) { ++ throw new StreamConstraintsException(String.format("Depth (%d) exceeds the maximum allowed nesting depth (%d)", ++ depth, _maxNestingDepth)); ++ } ++ } ++ ++ /* ++ /********************************************************************** ++ /* Convenience methods for validation, token lengths ++ /********************************************************************** ++ */ ++ ++ /** ++ * Convenience method that can be used to verify that a floating-point ++ * number of specified length does not exceed maximum specified by this ++ * constraints object: if it does, a ++ * {@link StreamConstraintsException} ++ * is thrown. ++ * ++ * @param length Length of number in input units ++ * ++ * @throws StreamConstraintsException If length exceeds maximum ++ */ ++ public void validateFPLength(int length) throws StreamConstraintsException ++ { ++ if (length > _maxNumLen) { ++ throw new StreamConstraintsException(String.format("Number length (%d) exceeds the maximum length (%d)", ++ length, _maxNumLen)); ++ } ++ } ++ ++ /** ++ * Convenience method that can be used to verify that an integer ++ * number of specified length does not exceed maximum specific by this ++ * constraints object: if it does, a ++ * {@link StreamConstraintsException} ++ * is thrown. ++ * ++ * @param length Length of number in input units ++ * ++ * @throws StreamConstraintsException If length exceeds maximum ++ */ ++ public void validateIntegerLength(int length) throws StreamConstraintsException ++ { ++ if (length > _maxNumLen) { ++ throw new StreamConstraintsException(String.format("Number length (%d) exceeds the maximum length (%d)", ++ length, _maxNumLen)); ++ } ++ } ++ ++ /** ++ * Convenience method that can be used to verify that a String ++ * of specified length does not exceed maximum specific by this ++ * constraints object: if it does, a ++ * {@link StreamConstraintsException} ++ * is thrown. ++ * ++ * @param length Length of string in input units ++ * ++ * @throws StreamConstraintsException If length exceeds maximum ++ */ ++ public void validateStringLength(int length) throws StreamConstraintsException ++ { ++ if (length > _maxStringLen) { ++ throw new StreamConstraintsException(String.format("String length (%d) exceeds the maximum length (%d)", ++ length, _maxStringLen)); ++ } ++ } ++ ++ /* ++ /********************************************************************** ++ /* Convenience methods for validation, other ++ /********************************************************************** ++ */ ++ ++ /** ++ * Convenience method that can be used to verify that a conversion to ++ * {@link java.math.BigInteger} ++ * {@link StreamConstraintsException} ++ * is thrown. ++ * ++ * @param scale Scale (possibly negative) of {@link java.math.BigDecimal} to convert ++ * ++ * @throws StreamConstraintsException If magnitude (absolute value) of scale exceeds maximum ++ * allowed ++ */ ++ public void validateBigIntegerScale(int scale) throws StreamConstraintsException ++ { ++ final int absScale = Math.abs(scale); ++ final int limit = MAX_BIGINT_SCALE_MAGNITUDE; ++ ++ if (absScale > limit) { ++ throw new StreamConstraintsException(String.format( ++ "BigDecimal scale (%d) magnitude exceeds maximum allowed (%d)", ++ scale, limit)); ++ } ++ } ++} +diff --git a/src/main/java/com/fasterxml/jackson/core/StreamReadFeature.java b/src/main/java/com/fasterxml/jackson/core/StreamReadFeature.java +index fb1fca0..f44937d 100644 +--- a/src/main/java/com/fasterxml/jackson/core/StreamReadFeature.java ++++ b/src/main/java/com/fasterxml/jackson/core/StreamReadFeature.java +@@ -93,7 +93,7 @@ public enum StreamReadFeature + + /** + * Feature that determines whether we use the built-in {@link Double#parseDouble(String)} code to parse +- * doubles or if we use {@link com.fasterxml.jackson.core.io.doubleparser} ++ * doubles or if we use {@code FastDoubleParser} + * instead. + *

+ * This setting is disabled by default. +diff --git a/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java b/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java +index d9055eb..757b627 100644 +--- a/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java ++++ b/src/main/java/com/fasterxml/jackson/core/TSFBuilder.java +@@ -30,7 +30,7 @@ public abstract class TSFBuilder +- * As of 2.14, this only applies to {@link BigInteger} and {@link BigDecimal}. + * + * @since 2.14 + */ +@@ -236,7 +239,7 @@ public abstract class ParserBase extends ParserMinimalBase + + /** + * Length of the exponent part of the number, if any, not +- * including 'e' marker or sign, just digits. ++ * including 'e' marker or sign, just digits. + * Not used for pure integer values. + */ + protected int _expLength; +@@ -250,7 +253,10 @@ public abstract class ParserBase extends ParserMinimalBase + protected ParserBase(IOContext ctxt, int features) { + super(features); + _ioContext = ctxt; +- _textBuffer = ctxt.constructTextBuffer(); ++ final StreamReadConstraints streamReadConstraints = ctxt.streamReadConstraints(); ++ _streamReadConstraints = streamReadConstraints == null ? ++ StreamReadConstraints.defaults() : streamReadConstraints; ++ _textBuffer = ctxt.constructReadConstrainedTextBuffer(); + DupDetector dups = Feature.STRICT_DUPLICATE_DETECTION.enabledIn(features) + ? DupDetector.rootDetector(this) : null; + _parsingContext = JsonReadContext.createRootContext(dups); +@@ -267,7 +273,7 @@ public abstract class ParserBase extends ParserMinimalBase + public void setCurrentValue(Object v) { + _parsingContext.setCurrentValue(v); + } +- ++ + /* + /********************************************************** + /* Overrides for Feature handling +@@ -329,7 +335,7 @@ public abstract class ParserBase extends ParserMinimalBase + protected void _checkStdFeatureChanges(int newFeatureFlags, int changedFeatures) + { + int f = Feature.STRICT_DUPLICATE_DETECTION.getMask(); +- ++ + if ((changedFeatures & f) != 0) { + if ((newFeatureFlags & f) != 0) { + if (_parsingContext.getDupDetector() == null) { +@@ -346,7 +352,7 @@ public abstract class ParserBase extends ParserMinimalBase + /* JsonParser impl + /********************************************************** + */ +- ++ + /** + * Method that can be called to get the name associated with + * the current event. +@@ -428,7 +434,7 @@ public abstract class ParserBase extends ParserMinimalBase + + @Override + public boolean hasTextCharacters() { +- if (_currToken == JsonToken.VALUE_STRING) { return true; } // usually true ++ if (_currToken == JsonToken.VALUE_STRING) { return true; } // usually true + if (_currToken == JsonToken.FIELD_NAME) { return _nameCopied; } + return false; + } +@@ -469,7 +475,7 @@ public abstract class ParserBase extends ParserMinimalBase + */ + + protected abstract void _closeInput() throws IOException; +- ++ + /* + /********************************************************** + /* Low-level reading, other +@@ -493,7 +499,7 @@ public abstract class ParserBase extends ParserMinimalBase + _ioContext.releaseNameCopyBuffer(buf); + } + } +- ++ + /** + * Method called when an EOF is encountered between tokens. + * If so, it may be a legitimate EOF, but only iff there +@@ -529,7 +535,7 @@ public abstract class ParserBase extends ParserMinimalBase + /* Internal/package methods: shared/reusable builders + /********************************************************** + */ +- ++ + public ByteArrayBuilder _getByteArrayBuilder() + { + if (_byteArrayBuilder == null) { +@@ -547,36 +553,44 @@ public abstract class ParserBase extends ParserMinimalBase + */ + + // // // Life-cycle of number-parsing +- ++ + protected final JsonToken reset(boolean negative, int intLen, int fractLen, int expLen) ++ throws IOException + { + if (fractLen < 1 && expLen < 1) { // integer + return resetInt(negative, intLen); + } + return resetFloat(negative, intLen, fractLen, expLen); + } +- ++ + protected final JsonToken resetInt(boolean negative, int intLen) ++ throws IOException + { ++ // May throw StreamConstraintsException: ++ _streamReadConstraints.validateIntegerLength(intLen); + _numberNegative = negative; + _intLength = intLen; + _fractLength = 0; + _expLength = 0; +- _numTypesValid = NR_UNKNOWN; // to force parsing ++ _numTypesValid = NR_UNKNOWN; // to force decoding + return JsonToken.VALUE_NUMBER_INT; + } +- ++ + protected final JsonToken resetFloat(boolean negative, int intLen, int fractLen, int expLen) ++ throws IOException + { ++ // May throw StreamConstraintsException: ++ _streamReadConstraints.validateFPLength(intLen + fractLen + expLen); + _numberNegative = negative; + _intLength = intLen; + _fractLength = fractLen; + _expLength = expLen; +- _numTypesValid = NR_UNKNOWN; // to force parsing ++ _numTypesValid = NR_UNKNOWN; // to force decoding + return JsonToken.VALUE_NUMBER_FLOAT; + } +- ++ + protected final JsonToken resetAsNaN(String valueStr, double value) ++ throws IOException + { + _textBuffer.resetWithString(valueStr); + _numberDouble = value; +@@ -585,12 +599,10 @@ public abstract class ParserBase extends ParserMinimalBase + } + + @Override +- public boolean isNaN() { ++ public boolean isNaN() throws IOException { + if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) { + if ((_numTypesValid & NR_DOUBLE) != 0) { +- // 10-Mar-2017, tatu: Alas, `Double.isFinite(d)` only added in JDK 8 +- double d = _numberDouble; +- return Double.isNaN(d) || Double.isInfinite(d); ++ return !Double.isFinite(_getNumberDouble()); + } + } + return false; +@@ -628,12 +640,12 @@ public abstract class ParserBase extends ParserMinimalBase + return _getBigDecimal(); + } + if ((_numTypesValid & NR_FLOAT) != 0) { +- return _numberFloat; ++ return _getNumberFloat(); + } + if ((_numTypesValid & NR_DOUBLE) == 0) { // sanity check + _throwInternal(); + } +- return _numberDouble; ++ return _getNumberDouble(); + } + + // NOTE: mostly copied from above +@@ -663,12 +675,56 @@ public abstract class ParserBase extends ParserMinimalBase + return _getBigDecimal(); + } + if ((_numTypesValid & NR_FLOAT) != 0) { +- return _numberFloat; ++ return _getNumberFloat(); + } + if ((_numTypesValid & NR_DOUBLE) == 0) { // sanity check + _throwInternal(); + } +- return _numberDouble; ++ return _getNumberDouble(); ++ } ++ ++ @Override // since 2.15 ++ public Object getNumberValueDeferred() throws IOException ++ { ++ if (_currToken == JsonToken.VALUE_NUMBER_INT) { ++ if (_numTypesValid == NR_UNKNOWN) { ++ _parseNumericValue(NR_UNKNOWN); ++ } ++ if ((_numTypesValid & NR_INT) != 0) { ++ return _numberInt; ++ } ++ if ((_numTypesValid & NR_LONG) != 0) { ++ return _numberLong; ++ } ++ if ((_numTypesValid & NR_BIGINT) != 0) { ++ // from _getBigInteger() ++ if (_numberBigInt != null) { ++ return _numberBigInt; ++ } else if (_numberString != null) { ++ return _numberString; ++ } ++ return _getBigInteger(); // will fail ++ } ++ _throwInternal(); ++ } ++ if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) { ++ // Ok this gets tricky since flags are not set quite as with ++ // integers ++ if ((_numTypesValid & NR_BIGDECIMAL) != 0) { ++ return _getBigDecimal(); ++ } ++ if ((_numTypesValid & NR_DOUBLE) != 0) { // sanity check ++ return _getNumberDouble(); ++ } ++ if ((_numTypesValid & NR_FLOAT) != 0) { ++ return _getNumberFloat(); ++ } ++ // Should be able to rely on this; might want to set _numberString ++ // but state keeping looks complicated so don't do that yet ++ return _textBuffer.contentsAsString(); ++ } ++ // We'll just force exception by: ++ return getNumberValue(); + } + + @Override +@@ -686,7 +742,7 @@ public abstract class ParserBase extends ParserMinimalBase + } + return NumberType.BIG_INTEGER; + } +- ++ + /* And then floating point types. Here optimal type + * needs to be big decimal, to avoid losing any data? + * However... using BD is slow, so let's allow returning +@@ -701,7 +757,7 @@ public abstract class ParserBase extends ParserMinimalBase + } + return NumberType.DOUBLE; + } +- ++ + @Override + public int getIntValue() throws IOException + { +@@ -715,7 +771,7 @@ public abstract class ParserBase extends ParserMinimalBase + } + return _numberInt; + } +- ++ + @Override + public long getLongValue() throws IOException + { +@@ -729,7 +785,7 @@ public abstract class ParserBase extends ParserMinimalBase + } + return _numberLong; + } +- ++ + @Override + public BigInteger getBigIntegerValue() throws IOException + { +@@ -743,7 +799,7 @@ public abstract class ParserBase extends ParserMinimalBase + } + return _getBigInteger(); + } +- ++ + @Override + public float getFloatValue() throws IOException + { +@@ -763,9 +819,9 @@ public abstract class ParserBase extends ParserMinimalBase + convertNumberToFloat(); + } + } +- return _numberFloat; ++ return _getNumberFloat(); + } +- ++ + @Override + public double getDoubleValue() throws IOException + { +@@ -777,9 +833,9 @@ public abstract class ParserBase extends ParserMinimalBase + convertNumberToDouble(); + } + } +- return _numberDouble; ++ return _getNumberDouble(); + } +- ++ + @Override + public BigDecimal getDecimalValue() throws IOException + { +@@ -794,6 +850,11 @@ public abstract class ParserBase extends ParserMinimalBase + return _getBigDecimal(); + } + ++ @Override // @since 2.15 ++ public StreamReadConstraints streamReadConstraints() { ++ return _streamReadConstraints; ++ } ++ + /* + /********************************************************** + /* Conversion from textual to numeric representation +@@ -826,8 +887,7 @@ public abstract class ParserBase extends ParserMinimalBase + final int len = _intLength; + // First: optimization for simple int + if (len <= 9) { +- int i = _textBuffer.contentsAsInt(_numberNegative); +- _numberInt = i; ++ _numberInt = _textBuffer.contentsAsInt(_numberNegative); + _numTypesValid = NR_INT; + return; + } +@@ -898,58 +958,42 @@ public abstract class ParserBase extends ParserMinimalBase + * still be constructed correctly at any point since we do + * retain textual representation + */ +- try { +- if (expType == NR_BIGDECIMAL) { +- _numberBigDecimal = null; +- _numberString = _textBuffer.contentsAsString(); +- _numTypesValid = NR_BIGDECIMAL; +- } else if (expType == NR_FLOAT) { +- _numberFloat = _textBuffer.contentsAsFloat(isEnabled(Feature.USE_FAST_DOUBLE_PARSER)); +- _numTypesValid = NR_FLOAT; +- } else { +- // Otherwise double has to do +- _numberDouble = _textBuffer.contentsAsDouble(isEnabled(Feature.USE_FAST_DOUBLE_PARSER)); +- _numTypesValid = NR_DOUBLE; +- } +- } catch (NumberFormatException nex) { +- // Can this ever occur? Due to overflow, maybe? +- _wrapError("Malformed numeric value ("+_longNumberDesc(_textBuffer.contentsAsString())+")", nex); ++ if (expType == NR_BIGDECIMAL) { ++ // 04-Dec-2022, tatu: Let's defer actual decoding until it is certain ++ // value is actually needed. ++ _numberBigDecimal = null; ++ _numberString = _textBuffer.contentsAsString(); ++ _numTypesValid = NR_BIGDECIMAL; ++ } else if (expType == NR_FLOAT) { ++ _numberFloat = 0.0f; ++ _numberString = _textBuffer.contentsAsString(); ++ _numTypesValid = NR_FLOAT; ++ } else { ++ // Otherwise double has to do ++ // 04-Dec-2022, tatu: We can get all kinds of values here, NR_DOUBLE ++ // but also NR_INT or even NR_UNKNOWN. Shouldn't we try further ++ // deferring some typing? ++ _numberDouble = 0.0; ++ _numberString = _textBuffer.contentsAsString(); ++ _numTypesValid = NR_DOUBLE; + } + } + + private void _parseSlowInt(int expType) throws IOException + { +- String numStr = _textBuffer.contentsAsString(); +- try { +- int len = _intLength; +- char[] buf = _textBuffer.getTextBuffer(); +- int offset = _textBuffer.getTextOffset(); +- if (_numberNegative) { +- ++offset; +- } +- // Some long cases still... +- if (NumberInput.inLongRange(buf, offset, len, _numberNegative)) { +- // Probably faster to construct a String, call parse, than to use BigInteger +- _numberLong = Long.parseLong(numStr); +- _numTypesValid = NR_LONG; +- } else { +- // 16-Oct-2018, tatu: Need to catch "too big" early due to [jackson-core#488] +- if ((expType == NR_INT) || (expType == NR_LONG)) { +- _reportTooLongIntegral(expType, numStr); +- } +- if ((expType == NR_DOUBLE) || (expType == NR_FLOAT)) { +- _numberDouble = NumberInput.parseDouble(numStr, isEnabled(Feature.USE_FAST_DOUBLE_PARSER)); +- _numTypesValid = NR_DOUBLE; +- } else { +- // nope, need the heavy guns... (rare case) - since Jackson v2.14, BigInteger parsing is lazy +- _numberBigInt = null; +- _numberString = numStr; +- _numTypesValid = NR_BIGINT; +- } +- } +- } catch (NumberFormatException nex) { +- // Can this ever occur? Due to overflow, maybe? +- _wrapError("Malformed numeric value ("+_longNumberDesc(numStr)+")", nex); ++ final String numStr = _textBuffer.contentsAsString(); ++ // 16-Oct-2018, tatu: Need to catch "too big" early due to [jackson-core#488] ++ if ((expType == NR_INT) || (expType == NR_LONG)) { ++ _reportTooLongIntegral(expType, numStr); ++ } ++ if ((expType == NR_DOUBLE) || (expType == NR_FLOAT)) { ++ _numberString = numStr; ++ _numTypesValid = NR_DOUBLE; ++ } else { ++ // nope, need the heavy guns... (rare case) - since Jackson v2.14, BigInteger parsing is lazy ++ _numberBigInt = null; ++ _numberString = numStr; ++ _numTypesValid = NR_BIGINT; + } + } + +@@ -967,13 +1011,13 @@ public abstract class ParserBase extends ParserMinimalBase + /********************************************************** + /* Numeric conversions + /********************************************************** +- */ +- ++ */ ++ + protected void convertNumberToInt() throws IOException + { + // First, converting from long ought to be easy + if ((_numTypesValid & NR_LONG) != 0) { +- // Let's verify it's lossless conversion by simple roundtrip ++ // Let's verify its lossless conversion by simple roundtrip + int result = (int) _numberLong; + if (((long) result) != _numberLong) { + reportOverflowInt(getText(), currentToken()); +@@ -988,10 +1032,11 @@ public abstract class ParserBase extends ParserMinimalBase + _numberInt = bigInteger.intValue(); + } else if ((_numTypesValid & NR_DOUBLE) != 0) { + // Need to check boundaries +- if (_numberDouble < MIN_INT_D || _numberDouble > MAX_INT_D) { ++ final double d = _getNumberDouble(); ++ if (d < MIN_INT_D || d > MAX_INT_D) { + reportOverflowInt(); + } +- _numberInt = (int) _numberDouble; ++ _numberInt = (int) d; + } else if ((_numTypesValid & NR_BIGDECIMAL) != 0) { + final BigDecimal bigDecimal = _getBigDecimal(); + if (BD_MIN_INT.compareTo(bigDecimal) > 0 +@@ -1004,7 +1049,7 @@ public abstract class ParserBase extends ParserMinimalBase + } + _numTypesValid |= NR_INT; + } +- ++ + protected void convertNumberToLong() throws IOException + { + if ((_numTypesValid & NR_INT) != 0) { +@@ -1018,10 +1063,11 @@ public abstract class ParserBase extends ParserMinimalBase + _numberLong = bigInteger.longValue(); + } else if ((_numTypesValid & NR_DOUBLE) != 0) { + // Need to check boundaries +- if (_numberDouble < MIN_LONG_D || _numberDouble > MAX_LONG_D) { ++ final double d = _getNumberDouble(); ++ if (d < MIN_LONG_D || d > MAX_LONG_D) { + reportOverflowLong(); + } +- _numberLong = (long) _numberDouble; ++ _numberLong = (long) d; + } else if ((_numTypesValid & NR_BIGDECIMAL) != 0) { + final BigDecimal bigDecimal = _getBigDecimal(); + if (BD_MIN_LONG.compareTo(bigDecimal) > 0 +@@ -1034,24 +1080,28 @@ public abstract class ParserBase extends ParserMinimalBase + } + _numTypesValid |= NR_LONG; + } +- ++ + protected void convertNumberToBigInteger() throws IOException + { + if ((_numTypesValid & NR_BIGDECIMAL) != 0) { + // here it'll just get truncated, no exceptions thrown +- _numberBigInt = _getBigDecimal().toBigInteger(); ++ _numberBigInt = _convertBigDecimalToBigInteger(_getBigDecimal()); + } else if ((_numTypesValid & NR_LONG) != 0) { + _numberBigInt = BigInteger.valueOf(_numberLong); + } else if ((_numTypesValid & NR_INT) != 0) { + _numberBigInt = BigInteger.valueOf(_numberInt); + } else if ((_numTypesValid & NR_DOUBLE) != 0) { +- _numberBigInt = BigDecimal.valueOf(_numberDouble).toBigInteger(); ++ if (_numberString != null) { ++ _numberBigInt = _convertBigDecimalToBigInteger(_getBigDecimal()); ++ } else { ++ _numberBigInt = _convertBigDecimalToBigInteger(BigDecimal.valueOf(_getNumberDouble())); ++ } + } else { + _throwInternal(); + } + _numTypesValid |= NR_BIGINT; + } +- ++ + protected void convertNumberToDouble() throws IOException + { + /* 05-Aug-2008, tatus: Important note: this MUST start with +@@ -1059,17 +1109,29 @@ public abstract class ParserBase extends ParserMinimalBase + * value is the original one (others get generated when + * requested) + */ +- ++ + if ((_numTypesValid & NR_BIGDECIMAL) != 0) { +- _numberDouble = _getBigDecimal().doubleValue(); ++ if (_numberString != null) { ++ _numberDouble = _getNumberDouble(); ++ } else { ++ _numberDouble = _getBigDecimal().doubleValue(); ++ } + } else if ((_numTypesValid & NR_BIGINT) != 0) { +- _numberDouble = _getBigInteger().doubleValue(); ++ if (_numberString != null) { ++ _numberDouble = _getNumberDouble(); ++ } else { ++ _numberDouble = _getBigInteger().doubleValue(); ++ } + } else if ((_numTypesValid & NR_LONG) != 0) { + _numberDouble = (double) _numberLong; + } else if ((_numTypesValid & NR_INT) != 0) { + _numberDouble = (double) _numberInt; + } else if ((_numTypesValid & NR_FLOAT) != 0) { +- _numberDouble = (double) _numberFloat; ++ if (_numberString != null) { ++ _numberDouble = _getNumberDouble(); ++ } else { ++ _numberDouble = (double) _getNumberFloat(); ++ } + } else { + _throwInternal(); + } +@@ -1085,21 +1147,33 @@ public abstract class ParserBase extends ParserMinimalBase + */ + + if ((_numTypesValid & NR_BIGDECIMAL) != 0) { +- _numberFloat = _getBigDecimal().floatValue(); ++ if (_numberString != null) { ++ _numberFloat = _getNumberFloat(); ++ } else { ++ _numberFloat = _getBigDecimal().floatValue(); ++ } + } else if ((_numTypesValid & NR_BIGINT) != 0) { +- _numberFloat = _getBigInteger().floatValue(); ++ if (_numberString != null) { ++ _numberFloat = _getNumberFloat(); ++ } else { ++ _numberFloat = _getBigInteger().floatValue(); ++ } + } else if ((_numTypesValid & NR_LONG) != 0) { + _numberFloat = (float) _numberLong; + } else if ((_numTypesValid & NR_INT) != 0) { + _numberFloat = (float) _numberInt; + } else if ((_numTypesValid & NR_DOUBLE) != 0) { +- _numberFloat = (float) _numberDouble; ++ if (_numberString != null) { ++ _numberFloat = _getNumberFloat(); ++ } else { ++ _numberFloat = (float) _getNumberDouble(); ++ } + } else { + _throwInternal(); + } + _numTypesValid |= NR_FLOAT; + } +- ++ + protected void convertNumberToBigDecimal() throws IOException + { + /* 05-Aug-2008, tatus: Important note: this MUST start with +@@ -1107,7 +1181,7 @@ public abstract class ParserBase extends ParserMinimalBase + * value is the original one (others get generated when + * requested) + */ +- ++ + if ((_numTypesValid & NR_DOUBLE) != 0) { + // Let's actually parse from String representation, to avoid + // rounding errors that non-decimal floating operations could incur +@@ -1124,19 +1198,32 @@ public abstract class ParserBase extends ParserMinimalBase + _numTypesValid |= NR_BIGDECIMAL; + } + ++ // @since 2.15 ++ protected BigInteger _convertBigDecimalToBigInteger(BigDecimal bigDec) throws IOException { ++ // 04-Apr-2022, tatu: wrt [core#968] Need to limit max scale magnitude ++ // (may throw StreamConstraintsException) ++ _streamReadConstraints.validateBigIntegerScale(bigDec.scale()); ++ return bigDec.toBigInteger(); ++ } ++ + /** + * Internal accessor that needs to be used for accessing number value of type + * {@link BigInteger} which -- as of 2.14 -- is typically lazily parsed. + * + * @since 2.14 + */ +- protected BigInteger _getBigInteger() { ++ protected BigInteger _getBigInteger() throws JsonParseException { + if (_numberBigInt != null) { + return _numberBigInt; + } else if (_numberString == null) { + throw new IllegalStateException("cannot get BigInteger from current parser state"); + } +- _numberBigInt = NumberInput.parseBigInteger(_numberString); ++ try { ++ // NOTE! Length of number string has been validated earlier ++ _numberBigInt = NumberInput.parseBigInteger(_numberString); ++ } catch (NumberFormatException nex) { ++ _wrapError("Malformed numeric value ("+_longNumberDesc(_numberString)+")", nex); ++ } + _numberString = null; + return _numberBigInt; + } +@@ -1147,17 +1234,78 @@ public abstract class ParserBase extends ParserMinimalBase + * + * @since 2.14 + */ +- protected BigDecimal _getBigDecimal() { ++ protected BigDecimal _getBigDecimal() throws JsonParseException { + if (_numberBigDecimal != null) { + return _numberBigDecimal; + } else if (_numberString == null) { + throw new IllegalStateException("cannot get BigDecimal from current parser state"); + } +- _numberBigDecimal = NumberInput.parseBigDecimal(_numberString); ++ try { ++ // NOTE! Length of number string has been validated earlier ++ _numberBigDecimal = NumberInput.parseBigDecimal(_numberString); ++ } catch (NumberFormatException nex) { ++ _wrapError("Malformed numeric value ("+_longNumberDesc(_numberString)+")", nex); ++ } + _numberString = null; + return _numberBigDecimal; + } +- ++ ++ /** ++ * Internal accessor that needs to be used for accessing number value of type ++ * {@code double} which -- as of 2.15 -- will be lazily parsed. ++ * ++ * @since 2.15 ++ */ ++ protected double _getNumberDouble() throws JsonParseException { ++ if (_numberString != null) { ++ try { ++ _numberDouble = NumberInput.parseDouble(_numberString, ++ isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER)); ++ } catch (NumberFormatException nex) { ++ _wrapError("Malformed numeric value ("+_longNumberDesc(_numberString)+")", nex); ++ } ++ _numberString = null; ++ } ++ return _numberDouble; ++ } ++ ++ /** ++ * Internal accessor that needs to be used for accessing number value of type ++ * {@code float} which -- as of 2.15 -- will be lazily parsed. ++ * ++ * @since 2.15 ++ */ ++ protected float _getNumberFloat() throws JsonParseException { ++ if (_numberString != null) { ++ try { ++ _numberFloat = NumberInput.parseFloat(_numberString, ++ isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER)); ++ } catch (NumberFormatException nex) { ++ _wrapError("Malformed numeric value ("+_longNumberDesc(_numberString)+")", nex); ++ } ++ _numberString = null; ++ } ++ return _numberFloat; ++ } ++ ++ /* ++ /********************************************************** ++ /* Internal/package methods: Context handling (2.15) ++ /********************************************************** ++ */ ++ ++ // @since 2.15 ++ protected void createChildArrayContext(final int lineNr, final int colNr) throws IOException { ++ _parsingContext = _parsingContext.createChildArrayContext(lineNr, colNr); ++ _streamReadConstraints.validateNestingDepth(_parsingContext.getNestingDepth()); ++ } ++ ++ // @since 2.15 ++ protected void createChildObjectContext(final int lineNr, final int colNr) throws IOException { ++ _parsingContext = _parsingContext.createChildObjectContext(lineNr, colNr); ++ _streamReadConstraints.validateNestingDepth(_parsingContext.getNestingDepth()); ++ } ++ + /* + /********************************************************** + /* Internal/package methods: Error reporting +@@ -1255,7 +1403,7 @@ public abstract class ParserBase extends ParserMinimalBase + protected char _decodeEscaped() throws IOException { + throw new UnsupportedOperationException(); + } +- ++ + protected final int _decodeBase64Escape(Base64Variant b64variant, int ch, int index) throws IOException + { + // 17-May-2011, tatu: As per [JACKSON-xxx], need to handle escaped chars +@@ -1278,7 +1426,7 @@ public abstract class ParserBase extends ParserMinimalBase + } + return bits; + } +- ++ + protected final int _decodeBase64Escape(Base64Variant b64variant, char ch, int index) throws IOException + { + if (ch != '\\') { +@@ -1301,7 +1449,7 @@ public abstract class ParserBase extends ParserMinimalBase + } + return bits; + } +- ++ + protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64variant, int ch, int bindex) throws IllegalArgumentException { + return reportInvalidBase64Char(b64variant, ch, bindex, null); + } +@@ -1394,5 +1542,4 @@ public abstract class ParserBase extends ParserMinimalBase + + // Can't declare as deprecated, for now, but shouldn't be needed + protected void _finishString() throws IOException { } +- + } +diff --git a/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java b/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java +index 188d700..1bdba36 100644 +--- a/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java ++++ b/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java +@@ -456,6 +456,7 @@ public abstract class ParserMinimalBase extends JsonParser + if (_hasTextualNull(str)) { + return 0L; + } ++ streamReadConstraints().validateFPLength(str.length()); + return NumberInput.parseAsDouble(str, defaultValue); + case ID_NUMBER_INT: + case ID_NUMBER_FLOAT: +@@ -543,6 +544,15 @@ public abstract class ParserMinimalBase extends JsonParser + */ + protected boolean _hasTextualNull(String value) { return "null".equals(value); } + ++ /** ++ * Get the constraints to apply when performing streaming reads. ++ * ++ * @since 2.15 ++ */ ++ public StreamReadConstraints streamReadConstraints() { ++ return StreamReadConstraints.defaults(); ++ } ++ + /* + /********************************************************** + /* Error reporting +diff --git a/src/main/java/com/fasterxml/jackson/core/exc/StreamConstraintsException.java b/src/main/java/com/fasterxml/jackson/core/exc/StreamConstraintsException.java +new file mode 100644 +index 0000000..f8c5687 +--- /dev/null ++++ b/src/main/java/com/fasterxml/jackson/core/exc/StreamConstraintsException.java +@@ -0,0 +1,25 @@ ++package com.fasterxml.jackson.core.exc; ++ ++import com.fasterxml.jackson.core.JsonLocation; ++import com.fasterxml.jackson.core.JsonProcessingException; ++ ++/** ++ * Exception type used to indicate violations of stream constraints ++ * (for example {@link com.fasterxml.jackson.core.StreamReadConstraints}) ++ * when reading or writing content. ++ * ++ * @since 2.15 ++ */ ++public class StreamConstraintsException ++ extends JsonProcessingException ++{ ++ private final static long serialVersionUID = 2L; ++ ++ public StreamConstraintsException(String msg) { ++ super(msg); ++ } ++ ++ public StreamConstraintsException(String msg, JsonLocation loc) { ++ super(msg, loc); ++ } ++} +diff --git a/src/main/java/com/fasterxml/jackson/core/io/IOContext.java b/src/main/java/com/fasterxml/jackson/core/io/IOContext.java +index f57c1d3..53a7c21 100644 +--- a/src/main/java/com/fasterxml/jackson/core/io/IOContext.java ++++ b/src/main/java/com/fasterxml/jackson/core/io/IOContext.java +@@ -1,7 +1,9 @@ + package com.fasterxml.jackson.core.io; + + import com.fasterxml.jackson.core.JsonEncoding; ++import com.fasterxml.jackson.core.StreamReadConstraints; + import com.fasterxml.jackson.core.util.BufferRecycler; ++import com.fasterxml.jackson.core.util.ReadConstrainedTextBuffer; + import com.fasterxml.jackson.core.util.TextBuffer; + + /** +@@ -60,6 +62,8 @@ public class IOContext + */ + protected final BufferRecycler _bufferRecycler; + ++ protected final StreamReadConstraints _streamReadConstraints; ++ + /** + * Reference to the allocated I/O buffer for low-level input reading, + * if any allocated. +@@ -71,7 +75,7 @@ public class IOContext + * encoding-related buffering. + */ + protected byte[] _writeEncodingBuffer; +- ++ + /** + * Reference to the buffer allocated for temporary use with + * base64 encoding or decoding. +@@ -108,26 +112,51 @@ public class IOContext + + /** + * Main constructor to use. +- * ++ * ++ * @param src constraints for streaming reads + * @param br BufferRecycler to use, if any ({@code null} if none) + * @param contentRef Input source reference for location reporting + * @param managedResource Whether input source is managed (owned) by Jackson library + * +- * @since 2.13 ++ * @since 2.15 + */ +- public IOContext(BufferRecycler br, ContentReference contentRef, boolean managedResource) ++ public IOContext(StreamReadConstraints src, BufferRecycler br, ++ ContentReference contentRef, boolean managedResource) + { ++ _streamReadConstraints = (src == null) ? ++ StreamReadConstraints.defaults() : src; + _bufferRecycler = br; + _contentReference = contentRef; + _sourceRef = contentRef.getRawContent(); + _managedResource = managedResource; + } + ++ /** ++ * @param br BufferRecycler to use, if any ({@code null} if none) ++ * @param contentRef Input source reference for location reporting ++ * @param managedResource Whether input source is managed (owned) by Jackson library ++ * ++ * @since 2.13 ++ */ ++ @Deprecated // since 2.15 ++ public IOContext(BufferRecycler br, ContentReference contentRef, boolean managedResource) ++ { ++ this(null, br, contentRef, managedResource); ++ } ++ + @Deprecated // since 2.13 + public IOContext(BufferRecycler br, Object rawContent, boolean managedResource) { + this(br, ContentReference.rawReference(rawContent), managedResource); + } + ++ /** ++ * @return constraints for streaming reads ++ * @since 2.15 ++ */ ++ public StreamReadConstraints streamReadConstraints() { ++ return _streamReadConstraints; ++ } ++ + public void setEncoding(JsonEncoding enc) { + _encoding = enc; + } +@@ -149,7 +178,7 @@ public class IOContext + /** + * Accessor for getting (some) information about input source, mostly + * usable for error reporting purposes. +- * ++ * + * @return Reference to input source + * + * @since 2.13 +@@ -175,6 +204,10 @@ public class IOContext + return new TextBuffer(_bufferRecycler); + } + ++ public TextBuffer constructReadConstrainedTextBuffer() { ++ return new ReadConstrainedTextBuffer(_streamReadConstraints, _bufferRecycler); ++ } ++ + /** + * Method for recycling or allocation byte buffer of "read I/O" type. + *

+@@ -258,7 +291,7 @@ public class IOContext + _verifyAlloc(_base64Buffer); + return (_base64Buffer = _bufferRecycler.allocByteBuffer(BufferRecycler.BYTE_BASE64_CODEC_BUFFER, minSize)); + } +- ++ + public char[] allocTokenBuffer() { + _verifyAlloc(_tokenCBuffer); + return (_tokenCBuffer = _bufferRecycler.allocCharBuffer(BufferRecycler.CHAR_TOKEN_BUFFER)); +@@ -269,7 +302,7 @@ public class IOContext + _verifyAlloc(_tokenCBuffer); + return (_tokenCBuffer = _bufferRecycler.allocCharBuffer(BufferRecycler.CHAR_TOKEN_BUFFER, minSize)); + } +- ++ + public char[] allocConcatBuffer() { + _verifyAlloc(_concatCBuffer); + return (_concatCBuffer = _bufferRecycler.allocCharBuffer(BufferRecycler.CHAR_CONCAT_BUFFER)); +@@ -313,7 +346,7 @@ public class IOContext + _bufferRecycler.releaseByteBuffer(BufferRecycler.BYTE_BASE64_CODEC_BUFFER, buf); + } + } +- ++ + public void releaseTokenBuffer(char[] buf) { + if (buf != null) { + _verifyRelease(buf, _tokenCBuffer); +diff --git a/src/main/java/com/fasterxml/jackson/core/io/JsonStringEncoder.java b/src/main/java/com/fasterxml/jackson/core/io/JsonStringEncoder.java +index 75bd5df..8748745 100644 +--- a/src/main/java/com/fasterxml/jackson/core/io/JsonStringEncoder.java ++++ b/src/main/java/com/fasterxml/jackson/core/io/JsonStringEncoder.java +@@ -1,5 +1,6 @@ + package com.fasterxml.jackson.core.io; + ++import java.io.IOException; + import java.util.Arrays; + + import com.fasterxml.jackson.core.util.ByteArrayBuilder; +@@ -47,7 +48,7 @@ public final class JsonStringEncoder + + // Since 2.10 we have stateless singleton and NO fancy ThreadLocal/SofRef caching!!! + private final static JsonStringEncoder instance = new JsonStringEncoder(); +- ++ + public JsonStringEncoder() { } + + /** +@@ -97,7 +98,12 @@ public final class JsonStringEncoder + if (textBuffer == null) { + textBuffer = TextBuffer.fromInitial(outputBuffer); + } +- outputBuffer = textBuffer.finishCurrentSegment(); ++ try { ++ outputBuffer = textBuffer.finishCurrentSegment(); ++ } catch (IOException e) { ++ // IOException won't happen here, can only occur when ReadConstrainedTextBuffer is used ++ throw new IllegalStateException(e); ++ } + outPtr = 0; + } + outputBuffer[outPtr++] = c; +@@ -105,7 +111,7 @@ public final class JsonStringEncoder + break outer; + } + } +- // something to escape; 2 or 6-char variant? ++ // something to escape; 2 or 6-char variant? + if (qbuf == null) { + qbuf = _qbuf(); + } +@@ -123,7 +129,12 @@ public final class JsonStringEncoder + if (textBuffer == null) { + textBuffer = TextBuffer.fromInitial(outputBuffer); + } +- outputBuffer = textBuffer.finishCurrentSegment(); ++ try { ++ outputBuffer = textBuffer.finishCurrentSegment(); ++ } catch (IOException e) { ++ // IOException won't happen here, can only occur when ReadConstrainedTextBuffer is used ++ throw new IllegalStateException(e); ++ } + int second = length - first; + System.arraycopy(qbuf, first, outputBuffer, 0, second); + outPtr = second; +@@ -137,7 +148,12 @@ public final class JsonStringEncoder + return Arrays.copyOfRange(outputBuffer, 0, outPtr); + } + textBuffer.setCurrentLength(outPtr); +- return textBuffer.contentsAsArray(); ++ try { ++ return textBuffer.contentsAsArray(); ++ } catch (IOException e) { ++ // IOException won't happen here, can only occur when ReadConstrainedTextBuffer is used ++ throw new IllegalStateException(e); ++ } + } + + /** +@@ -165,7 +181,7 @@ public final class JsonStringEncoder + int inPtr = 0; + int outPtr = 0; + char[] qbuf = null; +- ++ + outer: + while (inPtr < inputLen) { + tight_loop: +@@ -178,7 +194,12 @@ public final class JsonStringEncoder + if (textBuffer == null) { + textBuffer = TextBuffer.fromInitial(outputBuffer); + } +- outputBuffer = textBuffer.finishCurrentSegment(); ++ try { ++ outputBuffer = textBuffer.finishCurrentSegment(); ++ } catch (IOException e) { ++ // IOException won't happen here, can only occur when ReadConstrainedTextBuffer is used ++ throw new IllegalStateException(e); ++ } + outPtr = 0; + } + outputBuffer[outPtr++] = c; +@@ -186,7 +207,7 @@ public final class JsonStringEncoder + break outer; + } + } +- // something to escape; 2 or 6-char variant? ++ // something to escape; 2 or 6-char variant? + if (qbuf == null) { + qbuf = _qbuf(); + } +@@ -204,7 +225,12 @@ public final class JsonStringEncoder + if (textBuffer == null) { + textBuffer = TextBuffer.fromInitial(outputBuffer); + } +- outputBuffer = textBuffer.finishCurrentSegment(); ++ try { ++ outputBuffer = textBuffer.finishCurrentSegment(); ++ } catch (IOException e) { ++ // IOException won't happen here, can only occur when ReadConstrainedTextBuffer is used ++ throw new IllegalStateException(e); ++ } + int second = length - first; + System.arraycopy(qbuf, first, outputBuffer, 0, second); + outPtr = second; +@@ -218,7 +244,12 @@ public final class JsonStringEncoder + return Arrays.copyOfRange(outputBuffer, 0, outPtr); + } + textBuffer.setCurrentLength(outPtr); +- return textBuffer.contentsAsArray(); ++ try { ++ return textBuffer.contentsAsArray(); ++ } catch (IOException e) { ++ // IOException won't happen here, can only occur when ReadConstrainedTextBuffer is used ++ throw new IllegalStateException(e); ++ } + } + + /** +@@ -282,7 +313,7 @@ public final class JsonStringEncoder + int outputPtr = 0; + byte[] outputBuffer = new byte[_initialByteBufSize(inputEnd)]; + ByteArrayBuilder bb = null; +- ++ + main: + while (inputPtr < inputEnd) { + final int[] escCodes = CharTypes.get7BitOutputEscapes(); +diff --git a/src/main/java/com/fasterxml/jackson/core/io/SegmentedStringWriter.java b/src/main/java/com/fasterxml/jackson/core/io/SegmentedStringWriter.java +index b2594d6..7c5285e 100644 +--- a/src/main/java/com/fasterxml/jackson/core/io/SegmentedStringWriter.java ++++ b/src/main/java/com/fasterxml/jackson/core/io/SegmentedStringWriter.java +@@ -13,8 +13,7 @@ import com.fasterxml.jackson.core.util.TextBuffer; + * if so, instance of this class can be given as the writer to + * JsonGenerator. + */ +-public final class SegmentedStringWriter extends Writer +-{ ++public final class SegmentedStringWriter extends Writer { + final private TextBuffer _buffer; + + public SegmentedStringWriter(BufferRecycler br) { +@@ -29,42 +28,55 @@ public final class SegmentedStringWriter extends Writer + */ + + @Override +- public Writer append(char c) { ++ public Writer append(char c) throws IOException { + write(c); + return this; + } + + @Override +- public Writer append(CharSequence csq) { ++ public Writer append(CharSequence csq) throws IOException { + String str = csq.toString(); + _buffer.append(str, 0, str.length()); + return this; + } + + @Override +- public Writer append(CharSequence csq, int start, int end) { ++ public Writer append(CharSequence csq, int start, int end) throws IOException { + String str = csq.subSequence(start, end).toString(); + _buffer.append(str, 0, str.length()); + return this; + } + +- @Override public void close() { } // NOP +- @Override public void flush() { } // NOP ++ @Override ++ public void close() { } // NOP + + @Override +- public void write(char[] cbuf) { _buffer.append(cbuf, 0, cbuf.length); } ++ public void flush() { } // NOP + + @Override +- public void write(char[] cbuf, int off, int len) { _buffer.append(cbuf, off, len); } ++ public void write(char[] cbuf) throws IOException { ++ _buffer.append(cbuf, 0, cbuf.length); ++ } + + @Override +- public void write(int c) { _buffer.append((char) c); } ++ public void write(char[] cbuf, int off, int len) throws IOException { ++ _buffer.append(cbuf, off, len); ++ } + + @Override +- public void write(String str) { _buffer.append(str, 0, str.length()); } ++ public void write(int c) throws IOException { ++ _buffer.append((char) c); ++ } + + @Override +- public void write(String str, int off, int len) { _buffer.append(str, off, len); } ++ public void write(String str) throws IOException { ++ _buffer.append(str, 0, str.length()); ++ } ++ ++ @Override ++ public void write(String str, int off, int len) throws IOException { ++ _buffer.append(str, off, len); ++ } + + /* + /********************************************************** +@@ -80,8 +92,10 @@ public final class SegmentedStringWriter extends Writer + * will just return an empty String. + * + * @return String that contains all aggregated content ++ * @throws IOException if there are general I/O or parse issues, including if the text is too large, ++ * see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} + */ +- public String getAndClear() { ++ public String getAndClear() throws IOException { + String result = _buffer.contentsAsString(); + _buffer.releaseBuffers(); + return result; +diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonReadContext.java b/src/main/java/com/fasterxml/jackson/core/json/JsonReadContext.java +index 2e5b4f3..db2dcc7 100644 +--- a/src/main/java/com/fasterxml/jackson/core/json/JsonReadContext.java ++++ b/src/main/java/com/fasterxml/jackson/core/json/JsonReadContext.java +@@ -61,6 +61,7 @@ public final class JsonReadContext extends JsonStreamContext + _lineNr = lineNr; + _columnNr = colNr; + _index = -1; ++ _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; + } + + /** +diff --git a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java +index cc1cfc5..ccbfa7d 100644 +--- a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java ++++ b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java +@@ -269,7 +269,7 @@ public class ReaderBasedJsonParser + protected void _loadMoreGuaranteed() throws IOException { + if (!_loadMore()) { _reportInvalidEOF(); } + } +- ++ + protected boolean _loadMore() throws IOException + { + if (_reader != null) { +@@ -310,6 +310,9 @@ public class ReaderBasedJsonParser + * if no current event (before first call to {@link #nextToken}, or + * after encountering end-of-input), returns null. + * Method can be called for any event. ++ * ++ * @throws IOException if there are general I/O or parse issues, including if the text is too large, ++ * see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} + */ + @Override + public final String getText() throws IOException +@@ -350,7 +353,7 @@ public class ReaderBasedJsonParser + } + return 0; + } +- ++ + // // // Let's override default impls for improved performance + + // @since 2.1 +@@ -386,7 +389,7 @@ public class ReaderBasedJsonParser + return super.getValueAsString(defValue); + } + +- protected final String _getText2(JsonToken t) { ++ protected final String _getText2(JsonToken t) throws IOException { + if (t == null) { + return null; + } +@@ -594,7 +597,7 @@ public class ReaderBasedJsonParser + if (ch == '"') { + decodedData >>= 4; + buffer[outputPtr++] = (byte) decodedData; +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + --_inputPtr; // to keep parser state bit more consistent + _handleBase64MissingPadding(b64variant); + } +@@ -634,7 +637,7 @@ public class ReaderBasedJsonParser + decodedData >>= 2; + buffer[outputPtr++] = (byte) (decodedData >> 8); + buffer[outputPtr++] = (byte) decodedData; +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + --_inputPtr; // to keep parser state bit more consistent + _handleBase64MissingPadding(b64variant); + } +@@ -749,13 +752,13 @@ public class ReaderBasedJsonParser + break; + case '[': + if (!inObject) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } + t = JsonToken.START_ARRAY; + break; + case '{': + if (!inObject) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + t = JsonToken.START_OBJECT; + break; +@@ -814,19 +817,19 @@ public class ReaderBasedJsonParser + return t; + } + +- private final JsonToken _nextAfterName() ++ private final JsonToken _nextAfterName() throws IOException + { + _nameCopied = false; // need to invalidate if it was copied + JsonToken t = _nextToken; + _nextToken = null; + + // !!! 16-Nov-2015, tatu: TODO: fix [databind#37], copy next location to current here +- ++ + // Also: may need to start new context? + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return (_currToken = t); + } +@@ -973,7 +976,7 @@ public class ReaderBasedJsonParser + _nextToken = JsonToken.VALUE_STRING; + return name; + } +- ++ + // Ok: we must have a value... what is it? + + JsonToken t; +@@ -1162,10 +1165,10 @@ public class ReaderBasedJsonParser + } + switch (i) { + case '[': +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + return (_currToken = JsonToken.START_ARRAY); + case '{': +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + return (_currToken = JsonToken.START_OBJECT); + case 't': + _matchToken("true", 1); +@@ -1198,10 +1201,10 @@ public class ReaderBasedJsonParser + /* + * This check proceeds only if the Feature.ALLOW_MISSING_VALUES is enabled + * The Check is for missing values. In case of missing values in an array, the next token will be either ',' or ']'. +- * This case, decrements the already incremented _inputPtr in the buffer in case of comma(,) ++ * This case, decrements the already incremented _inputPtr in the buffer in case of comma(,) + * so that the existing flow goes back to checking the next token which will be comma again and + * it continues the parsing. +- * Also the case returns NULL as current token in case of ',' or ']'. ++ * Also the case returns NULL as current token in case of ',' or ']'. + */ + // case ']': // 11-May-2020, tatu: related to [core#616], this should never be reached + case ',': +@@ -1232,9 +1235,9 @@ public class ReaderBasedJsonParser + return _textBuffer.contentsAsString(); + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return null; + } +@@ -1255,9 +1258,9 @@ public class ReaderBasedJsonParser + return getIntValue(); + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return defaultValue; + } +@@ -1278,9 +1281,9 @@ public class ReaderBasedJsonParser + return getLongValue(); + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return defaultValue; + } +@@ -1304,9 +1307,9 @@ public class ReaderBasedJsonParser + return Boolean.FALSE; + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return null; + } +@@ -1576,7 +1579,7 @@ public class ReaderBasedJsonParser + int intLen = 0; + char c = (_inputPtr < _inputEnd) ? _inputBuffer[_inputPtr++] + : getNextChar("No digit following minus sign", JsonToken.VALUE_NUMBER_INT); +- ++ + if (c == '0') { + c = _verifyNoLeadingZeroes(); + } +@@ -2807,7 +2810,7 @@ public class ReaderBasedJsonParser + } + ++_inputPtr; + } while (++i < len); +- ++ + // but let's also ensure we either get EOF, or non-alphanum char... + if (_inputPtr >= _inputEnd && !_loadMore()) { + return; +@@ -2896,7 +2899,7 @@ public class ReaderBasedJsonParser + if (ch == '"') { + decodedData >>= 4; + builder.append(decodedData); +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + --_inputPtr; // to keep parser state bit more consistent + _handleBase64MissingPadding(b64variant); + } +@@ -2936,7 +2939,7 @@ public class ReaderBasedJsonParser + if (ch == '"') { + decodedData >>= 2; + builder.appendTwoBytes(decodedData); +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + --_inputPtr; // to keep parser state bit more consistent + _handleBase64MissingPadding(b64variant); + } +diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java +index f0dff97..24ba310 100644 +--- a/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java ++++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java +@@ -5,6 +5,7 @@ import java.util.Arrays; + + import com.fasterxml.jackson.core.*; + import com.fasterxml.jackson.core.base.ParserBase; ++import com.fasterxml.jackson.core.exc.StreamConstraintsException; + import com.fasterxml.jackson.core.io.CharTypes; + import com.fasterxml.jackson.core.io.IOContext; + import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer; +@@ -28,7 +29,7 @@ import static com.fasterxml.jackson.core.JsonTokenId.*; + * available is the row (line) number (but even that is approximate in + * case of two-byte linefeeds -- it should work with single CR or LF tho) + * +- *

  • No white space validation: ++ *
  • No white space validation: + * checks are simplified NOT to check for control characters. + *
  • + * +@@ -38,8 +39,6 @@ import static com.fasterxml.jackson.core.JsonTokenId.*; + public class UTF8DataInputJsonParser + extends ParserBase + { +- final static byte BYTE_LF = (byte) '\n'; +- + @SuppressWarnings("deprecation") + private final static int FEAT_MASK_TRAILING_COMMA = Feature.ALLOW_TRAILING_COMMA.getMask(); + @SuppressWarnings("deprecation") +@@ -302,8 +301,8 @@ public class UTF8DataInputJsonParser + } + return super.getValueAsInt(defValue); + } +- +- protected final String _getText2(JsonToken t) ++ ++ protected final String _getText2(JsonToken t) throws IOException + { + if (t == null) { + return null; +@@ -327,7 +326,7 @@ public class UTF8DataInputJsonParser + { + if (_currToken != null) { // null only before/after document + switch (_currToken.id()) { +- ++ + case ID_FIELD_NAME: + if (!_nameCopied) { + String name = _parsingContext.getCurrentName(); +@@ -341,7 +340,7 @@ public class UTF8DataInputJsonParser + _nameCopied = true; + } + return _nameCopyBuffer; +- ++ + case ID_STRING: + if (_tokenIncomplete) { + _tokenIncomplete = false; +@@ -351,7 +350,7 @@ public class UTF8DataInputJsonParser + case ID_NUMBER_INT: + case ID_NUMBER_FLOAT: + return _textBuffer.getTextBuffer(); +- ++ + default: + return _currToken.asCharArray(); + } +@@ -403,7 +402,7 @@ public class UTF8DataInputJsonParser + } + return 0; + } +- ++ + @Override + public byte[] getBinaryValue(Base64Variant b64variant) throws IOException + { +@@ -505,7 +504,7 @@ public class UTF8DataInputJsonParser + if (ch == INT_QUOTE) { + decodedData >>= 4; + buffer[outputPtr++] = (byte) decodedData; +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + _handleBase64MissingPadding(b64variant); + } + break; +@@ -539,7 +538,7 @@ public class UTF8DataInputJsonParser + decodedData >>= 2; + buffer[outputPtr++] = (byte) (decodedData >> 8); + buffer[outputPtr++] = (byte) decodedData; +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + _handleBase64MissingPadding(b64variant); + } + break; +@@ -653,7 +652,7 @@ public class UTF8DataInputJsonParser + _tokenIncomplete = true; + _nextToken = JsonToken.VALUE_STRING; + return _currToken; +- } ++ } + JsonToken t; + + switch (i) { +@@ -716,10 +715,10 @@ public class UTF8DataInputJsonParser + } + switch (i) { + case '[': +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + return (_currToken = JsonToken.START_ARRAY); + case '{': +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + return (_currToken = JsonToken.START_OBJECT); + case 't': + _matchToken("true", 1); +@@ -753,18 +752,18 @@ public class UTF8DataInputJsonParser + } + return (_currToken = _handleUnexpectedValue(i)); + } +- +- private final JsonToken _nextAfterName() ++ ++ private final JsonToken _nextAfterName() throws IOException + { + _nameCopied = false; // need to invalidate if it was copied + JsonToken t = _nextToken; + _nextToken = null; +- ++ + // Also: may need to start new context? + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return (_currToken = t); + } +@@ -908,9 +907,9 @@ public class UTF8DataInputJsonParser + return _textBuffer.contentsAsString(); + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return null; + } +@@ -930,9 +929,9 @@ public class UTF8DataInputJsonParser + return getIntValue(); + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return defaultValue; + } +@@ -952,9 +951,9 @@ public class UTF8DataInputJsonParser + return getLongValue(); + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return defaultValue; + } +@@ -977,9 +976,9 @@ public class UTF8DataInputJsonParser + return Boolean.FALSE; + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return null; + } +@@ -1049,7 +1048,7 @@ public class UTF8DataInputJsonParser + { + char[] outBuf = _textBuffer.emptyAndGetCurrentSegment(); + int outPtr; +- ++ + // One special case: if first char is 0, must not be followed by a digit. + // Gets a bit tricky as we only want to retain 0 if it's the full value + if (c == INT_0) { +@@ -1289,7 +1288,7 @@ public class UTF8DataInputJsonParser + /* Internal methods, secondary parsing + /********************************************************** + */ +- ++ + protected final String _parseName(int i) throws IOException + { + if (i != INT_QUOTE) { +@@ -1329,7 +1328,7 @@ public class UTF8DataInputJsonParser + return findName(q, 3); + } + return parseName(q, i, 3); +- } ++ } + if (i == INT_QUOTE) { // 2 byte/char case or broken + return findName(q, 2); + } +@@ -1339,7 +1338,7 @@ public class UTF8DataInputJsonParser + return findName(q, 1); + } + return parseName(q, i, 1); +- } ++ } + if (q == INT_QUOTE) { // special case, "" + return ""; + } +@@ -1423,7 +1422,7 @@ public class UTF8DataInputJsonParser + } + return _parseLongName(i, q2, q3); + } +- ++ + private final String _parseLongName(int q, final int q2, int q3) throws IOException + { + _quadBuffer[0] = _quad1; +@@ -1493,7 +1492,7 @@ public class UTF8DataInputJsonParser + _quadBuffer[1] = q2; + return parseEscapedName(_quadBuffer, 2, q3, ch, lastQuadBytes); + } +- ++ + /* Slower parsing method which is generally branched to when + * an escape sequence is detected (or alternatively for long + * names, one crossing input buffer boundary). +@@ -1768,7 +1767,8 @@ public class UTF8DataInputJsonParser + /********************************************************** + */ + +- private final String findName(int q1, int lastQuadBytes) throws JsonParseException ++ private final String findName(int q1, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + q1 = pad(q1, lastQuadBytes); + // Usually we'll find it from the canonical symbol table already +@@ -1781,7 +1781,8 @@ public class UTF8DataInputJsonParser + return addName(_quadBuffer, 1, lastQuadBytes); + } + +- private final String findName(int q1, int q2, int lastQuadBytes) throws JsonParseException ++ private final String findName(int q1, int q2, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + q2 = pad(q2, lastQuadBytes); + // Usually we'll find it from the canonical symbol table already +@@ -1795,7 +1796,8 @@ public class UTF8DataInputJsonParser + return addName(_quadBuffer, 2, lastQuadBytes); + } + +- private final String findName(int q1, int q2, int q3, int lastQuadBytes) throws JsonParseException ++ private final String findName(int q1, int q2, int q3, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + q3 = pad(q3, lastQuadBytes); + String name = _symbols.findName(q1, q2, q3); +@@ -1808,8 +1810,9 @@ public class UTF8DataInputJsonParser + quads[2] = pad(q3, lastQuadBytes); + return addName(quads, 3, lastQuadBytes); + } +- +- private final String findName(int[] quads, int qlen, int lastQuad, int lastQuadBytes) throws JsonParseException ++ ++ private final String findName(int[] quads, int qlen, int lastQuad, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + if (qlen >= quads.length) { + _quadBuffer = quads = _growArrayBy(quads, quads.length); +@@ -1828,7 +1831,8 @@ public class UTF8DataInputJsonParser + * multi-byte chars (if any), and then construct Name instance + * and add it to the symbol table. + */ +- private final String addName(int[] quads, int qlen, int lastQuadBytes) throws JsonParseException ++ private final String addName(int[] quads, int qlen, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + /* Ok: must decode UTF-8 chars. No other validation is + * needed, since unescaping has been done earlier as necessary +@@ -1880,13 +1884,13 @@ public class UTF8DataInputJsonParser + if ((ix + needed) > byteLen) { + _reportInvalidEOF(" in field name", JsonToken.FIELD_NAME); + } +- ++ + // Ok, always need at least one more: + int ch2 = quads[ix >> 2]; // current quad, need to shift+mask + byteIx = (ix & 3); + ch2 = (ch2 >> ((3 - byteIx) << 3)); + ++ix; +- ++ + if ((ch2 & 0xC0) != 0x080) { + _reportInvalidOther(ch2); + } +@@ -1896,7 +1900,7 @@ public class UTF8DataInputJsonParser + byteIx = (ix & 3); + ch2 = (ch2 >> ((3 - byteIx) << 3)); + ++ix; +- ++ + if ((ch2 & 0xC0) != 0x080) { + _reportInvalidOther(ch2); + } +@@ -1986,7 +1990,7 @@ public class UTF8DataInputJsonParser + _finishString2(outBuf, outPtr, _inputData.readUnsignedByte()); + return _textBuffer.contentsAsString(); + } +- ++ + private final void _finishString2(char[] outBuf, int outPtr, int c) + throws IOException + { +@@ -2082,7 +2086,7 @@ public class UTF8DataInputJsonParser + if (c == INT_QUOTE) { + break main_loop; + } +- ++ + switch (codes[c]) { + case 1: // backslash + _decodeEscaped(); +@@ -2347,7 +2351,7 @@ public class UTF8DataInputJsonParser + + /** + * Alternative to {@link #_skipWS} that handles possible {@link EOFException} +- * caused by trying to read past the end of {@link InputData}. ++ * caused by trying to read past the end of the {@link DataInput}. + * + * @since 2.9 + */ +@@ -2383,7 +2387,7 @@ public class UTF8DataInputJsonParser + } + } + } +- ++ + private final int _skipWSComment(int i) throws IOException + { + while (true) { +@@ -2410,7 +2414,7 @@ public class UTF8DataInputJsonParser + */ + } + i = _inputData.readUnsignedByte(); +- } ++ } + } + + private final int _skipColon() throws IOException +@@ -2597,7 +2601,7 @@ public class UTF8DataInputJsonParser + } + } + } +- ++ + @Override + protected char _decodeEscaped() throws IOException + { +@@ -2647,7 +2651,7 @@ public class UTF8DataInputJsonParser + int c = firstByte & 0xFF; + if (c > 0x7F) { // if >= 0, is ascii and fine as is + int needed; +- ++ + // Ok; if we end here, we got multi-byte combination + if ((c & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF) + c &= 0x1F; +@@ -2669,7 +2673,7 @@ public class UTF8DataInputJsonParser + _reportInvalidOther(d & 0xFF); + } + c = (c << 6) | (d & 0x3F); +- ++ + if (needed > 1) { // needed == 1 means 2 bytes total + d = _inputData.readUnsignedByte(); // 3rd byte + if ((d & 0xC0) != 0x080) { +@@ -2816,7 +2820,7 @@ public class UTF8DataInputJsonParser + } + _reportError("Unrecognized token '"+sb.toString()+"': was expecting "+msg); + } +- ++ + protected void _reportInvalidChar(int c) + throws JsonParseException + { +@@ -2887,7 +2891,7 @@ public class UTF8DataInputJsonParser + } + } + int decodedData = bits; +- ++ + // then second base64 char; can't get padding yet, nor ws + ch = _inputData.readUnsignedByte(); + bits = b64variant.decodeBase64Char(ch); +@@ -2906,7 +2910,7 @@ public class UTF8DataInputJsonParser + if (ch == INT_QUOTE) { + decodedData >>= 4; + builder.append(decodedData); +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + _handleBase64MissingPadding(b64variant); + } + return builder.toByteArray(); +@@ -2938,7 +2942,7 @@ public class UTF8DataInputJsonParser + if (ch == INT_QUOTE) { + decodedData >>= 2; + builder.appendTwoBytes(decodedData); +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + _handleBase64MissingPadding(b64variant); + } + return builder.toByteArray(); +@@ -2980,7 +2984,7 @@ public class UTF8DataInputJsonParser + // } + + // No column tracking since we do not have pointers, DataInput has no offset +- ++ + return new JsonLocation(_contentReference(), -1L, -1L, _tokenInputRow, -1); + } + +diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java +index 30e42ab..48d71b6 100644 +--- a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java ++++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java +@@ -4,6 +4,7 @@ import java.io.*; + + import com.fasterxml.jackson.core.*; + import com.fasterxml.jackson.core.base.ParserBase; ++import com.fasterxml.jackson.core.exc.StreamConstraintsException; + import com.fasterxml.jackson.core.io.CharTypes; + import com.fasterxml.jackson.core.io.IOContext; + import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer; +@@ -89,7 +90,7 @@ public class UTF8StreamJsonParser + * + * @since 2.7 + */ +- protected int _nameStartOffset; ++ protected int _nameStartOffset; + + /** + * @since 2.7 +@@ -253,7 +254,7 @@ public class UTF8StreamJsonParser + if (space == 0) { // only occurs when we've been closed + return false; + } +- ++ + int count = _inputStream.read(_inputBuffer, 0, space); + if (count > 0) { + final int bufSize = _inputEnd; +@@ -366,7 +367,7 @@ public class UTF8StreamJsonParser + } + + // // // Let's override default impls for improved performance +- ++ + // @since 2.1 + @Override + public String getValueAsString() throws IOException +@@ -383,7 +384,7 @@ public class UTF8StreamJsonParser + } + return super.getValueAsString(null); + } +- ++ + // @since 2.1 + @Override + public String getValueAsString(String defValue) throws IOException +@@ -441,7 +442,7 @@ public class UTF8StreamJsonParser + return super.getValueAsInt(defValue); + } + +- protected final String _getText2(JsonToken t) ++ protected final String _getText2(JsonToken t) throws IOException + { + if (t == null) { + return null; +@@ -465,7 +466,7 @@ public class UTF8StreamJsonParser + { + if (_currToken != null) { // null only before/after document + switch (_currToken.id()) { +- ++ + case ID_FIELD_NAME: + if (!_nameCopied) { + String name = _parsingContext.getCurrentName(); +@@ -479,7 +480,7 @@ public class UTF8StreamJsonParser + _nameCopied = true; + } + return _nameCopyBuffer; +- ++ + case ID_STRING: + if (_tokenIncomplete) { + _tokenIncomplete = false; +@@ -489,7 +490,7 @@ public class UTF8StreamJsonParser + case ID_NUMBER_INT: + case ID_NUMBER_FLOAT: + return _textBuffer.getTextBuffer(); +- ++ + default: + return _currToken.asCharArray(); + } +@@ -502,7 +503,7 @@ public class UTF8StreamJsonParser + { + if (_currToken != null) { // null only before/after document + switch (_currToken.id()) { +- ++ + case ID_FIELD_NAME: + return _parsingContext.getCurrentName().length(); + case ID_STRING: +@@ -514,7 +515,7 @@ public class UTF8StreamJsonParser + case ID_NUMBER_INT: + case ID_NUMBER_FLOAT: + return _textBuffer.size(); +- ++ + default: + return _currToken.asCharArray().length; + } +@@ -652,7 +653,7 @@ public class UTF8StreamJsonParser + if (ch == INT_QUOTE) { + decodedData >>= 4; + buffer[outputPtr++] = (byte) decodedData; +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + --_inputPtr; // to keep parser state bit more consistent + _handleBase64MissingPadding(b64variant); + } +@@ -692,7 +693,7 @@ public class UTF8StreamJsonParser + decodedData >>= 2; + buffer[outputPtr++] = (byte) (decodedData >> 8); + buffer[outputPtr++] = (byte) decodedData; +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + --_inputPtr; // to keep parser state bit more consistent + _handleBase64MissingPadding(b64variant); + } +@@ -806,7 +807,7 @@ public class UTF8StreamJsonParser + _tokenIncomplete = true; + _nextToken = JsonToken.VALUE_STRING; + return _currToken; +- } ++ } + JsonToken t; + + switch (i) { +@@ -869,10 +870,10 @@ public class UTF8StreamJsonParser + } + switch (i) { + case '[': +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + return (_currToken = JsonToken.START_ARRAY); + case '{': +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + return (_currToken = JsonToken.START_OBJECT); + case 't': + _matchTrue(); +@@ -906,20 +907,20 @@ public class UTF8StreamJsonParser + } + return (_currToken = _handleUnexpectedValue(i)); + } +- +- private final JsonToken _nextAfterName() ++ ++ private final JsonToken _nextAfterName() throws IOException + { + _nameCopied = false; // need to invalidate if it was copied + JsonToken t = _nextToken; + _nextToken = null; + + // !!! 16-Nov-2015, tatu: TODO: fix [databind#37], copy next location to current here +- ++ + // Also: may need to start new context? + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return (_currToken = t); + } +@@ -990,7 +991,7 @@ public class UTF8StreamJsonParser + _nextTokenNotInObject(i); + return false; + } +- ++ + // // // This part differs, name parsing + _updateNameLocation(); + if (i == INT_QUOTE) { +@@ -1334,9 +1335,9 @@ public class UTF8StreamJsonParser + return _textBuffer.contentsAsString(); + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return null; + } +@@ -1357,9 +1358,9 @@ public class UTF8StreamJsonParser + return getIntValue(); + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return defaultValue; + } +@@ -1380,9 +1381,9 @@ public class UTF8StreamJsonParser + return getLongValue(); + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return defaultValue; + } +@@ -1406,9 +1407,9 @@ public class UTF8StreamJsonParser + return Boolean.FALSE; + } + if (t == JsonToken.START_ARRAY) { +- _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol); ++ createChildArrayContext(_tokenInputRow, _tokenInputCol); + } else if (t == JsonToken.START_OBJECT) { +- _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol); ++ createChildObjectContext(_tokenInputRow, _tokenInputCol); + } + return null; + } +@@ -1610,7 +1611,7 @@ public class UTF8StreamJsonParser + + // And there we have it! + return resetInt(negative, intPartLength); +- ++ + } + + // Method called when we have seen one zero, and want to ensure +@@ -1639,7 +1640,7 @@ public class UTF8StreamJsonParser + return INT_0; + } + ++_inputPtr; // skip previous zeroes +- if (ch != INT_0) { // followed by other number; return ++ if (ch != INT_0) { // followed by other number; return + break; + } + } +@@ -1786,7 +1787,7 @@ public class UTF8StreamJsonParser + /* Internal methods, secondary parsing + /********************************************************** + */ +- ++ + protected final String _parseName(int i) throws IOException + { + if (i != INT_QUOTE) { +@@ -1832,7 +1833,7 @@ public class UTF8StreamJsonParser + return findName(q, 3); + } + return parseName(q, i, 3); +- } ++ } + if (i == INT_QUOTE) { // 2 byte/char case or broken + return findName(q, 2); + } +@@ -1842,7 +1843,7 @@ public class UTF8StreamJsonParser + return findName(q, 1); + } + return parseName(q, i, 1); +- } ++ } + if (q == INT_QUOTE) { // special case, "" + return ""; + } +@@ -1929,7 +1930,7 @@ public class UTF8StreamJsonParser + } + return parseLongName(i, q2, q3); + } +- ++ + protected final String parseLongName(int q, final int q2, int q3) throws IOException + { + _quadBuffer[0] = _quad1; +@@ -2309,7 +2310,8 @@ public class UTF8StreamJsonParser + /********************************************************** + */ + +- private final String findName(int q1, int lastQuadBytes) throws JsonParseException ++ private final String findName(int q1, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + q1 = _padLastQuad(q1, lastQuadBytes); + // Usually we'll find it from the canonical symbol table already +@@ -2322,7 +2324,8 @@ public class UTF8StreamJsonParser + return addName(_quadBuffer, 1, lastQuadBytes); + } + +- private final String findName(int q1, int q2, int lastQuadBytes) throws JsonParseException ++ private final String findName(int q1, int q2, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + q2 = _padLastQuad(q2, lastQuadBytes); + // Usually we'll find it from the canonical symbol table already +@@ -2336,7 +2339,8 @@ public class UTF8StreamJsonParser + return addName(_quadBuffer, 2, lastQuadBytes); + } + +- private final String findName(int q1, int q2, int q3, int lastQuadBytes) throws JsonParseException ++ private final String findName(int q1, int q2, int q3, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + q3 = _padLastQuad(q3, lastQuadBytes); + String name = _symbols.findName(q1, q2, q3); +@@ -2349,8 +2353,9 @@ public class UTF8StreamJsonParser + quads[2] = _padLastQuad(q3, lastQuadBytes); + return addName(quads, 3, lastQuadBytes); + } +- +- private final String findName(int[] quads, int qlen, int lastQuad, int lastQuadBytes) throws JsonParseException ++ ++ private final String findName(int[] quads, int qlen, int lastQuad, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + if (qlen >= quads.length) { + _quadBuffer = quads = growArrayBy(quads, quads.length); +@@ -2368,7 +2373,8 @@ public class UTF8StreamJsonParser + * multi-byte chars (if any), and then construct Name instance + * and add it to the symbol table. + */ +- private final String addName(int[] quads, int qlen, int lastQuadBytes) throws JsonParseException ++ private final String addName(int[] quads, int qlen, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + /* Ok: must decode UTF-8 chars. No other validation is + * needed, since unescaping has been done earlier as necessary +@@ -2420,13 +2426,13 @@ public class UTF8StreamJsonParser + if ((ix + needed) > byteLen) { + _reportInvalidEOF(" in field name", JsonToken.FIELD_NAME); + } +- ++ + // Ok, always need at least one more: + int ch2 = quads[ix >> 2]; // current quad, need to shift+mask + byteIx = (ix & 3); + ch2 = (ch2 >> ((3 - byteIx) << 3)); + ++ix; +- ++ + if ((ch2 & 0xC0) != 0x080) { + _reportInvalidOther(ch2); + } +@@ -2436,7 +2442,7 @@ public class UTF8StreamJsonParser + byteIx = (ix & 3); + ch2 = (ch2 >> ((3 - byteIx) << 3)); + ++ix; +- ++ + if ((ch2 & 0xC0) != 0x080) { + _reportInvalidOther(ch2); + } +@@ -2490,11 +2496,11 @@ public class UTF8StreamJsonParser + protected void _loadMoreGuaranteed() throws IOException { + if (!_loadMore()) { _reportInvalidEOF(); } + } +- ++ + @Override + protected void _finishString() throws IOException + { +- // First, single tight loop for ASCII content, not split across input buffer boundary: ++ // First, single tight loop for ASCII content, not split across input buffer boundary: + int ptr = _inputPtr; + if (ptr >= _inputEnd) { + _loadMoreGuaranteed(); +@@ -2526,7 +2532,7 @@ public class UTF8StreamJsonParser + // @since 2.6 + protected String _finishAndReturnString() throws IOException + { +- // First, single tight loop for ASCII content, not split across input buffer boundary: ++ // First, single tight loop for ASCII content, not split across input buffer boundary: + int ptr = _inputPtr; + if (ptr >= _inputEnd) { + _loadMoreGuaranteed(); +@@ -2554,7 +2560,7 @@ public class UTF8StreamJsonParser + _finishString2(outBuf, outPtr); + return _textBuffer.contentsAsString(); + } +- ++ + private final void _finishString2(char[] outBuf, int outPtr) + throws IOException + { +@@ -2681,7 +2687,7 @@ public class UTF8StreamJsonParser + if (c == INT_QUOTE) { + break main_loop; + } +- ++ + switch (codes[c]) { + case 1: // backslash + _decodeEscaped(); +@@ -2726,7 +2732,7 @@ public class UTF8StreamJsonParser + * will be either ',' or ']'. This case, decrements the already incremented _inputPtr + * in the buffer in case of comma (`,`) so that the existing flow goes back to checking + * the next token which will be comma again and it parsing continues. +- * Also the case returns NULL as current token in case of ',' or ']'. ++ * Also the case returns NULL as current token in case of ',' or ']'. + */ + case ']': + if (!_parsingContext.inArray()) { +@@ -2928,7 +2934,7 @@ public class UTF8StreamJsonParser + int ptr = _inputPtr; + if ((ptr + 3) < _inputEnd) { + byte[] buf = _inputBuffer; +- if ((buf[ptr++] == 'r') ++ if ((buf[ptr++] == 'r') + && (buf[ptr++] == 'u') + && (buf[ptr++] == 'e')) { + int ch = buf[ptr] & 0xFF; +@@ -2946,7 +2952,7 @@ public class UTF8StreamJsonParser + int ptr = _inputPtr; + if ((ptr + 4) < _inputEnd) { + byte[] buf = _inputBuffer; +- if ((buf[ptr++] == 'a') ++ if ((buf[ptr++] == 'a') + && (buf[ptr++] == 'l') + && (buf[ptr++] == 's') + && (buf[ptr++] == 'e')) { +@@ -2965,7 +2971,7 @@ public class UTF8StreamJsonParser + int ptr = _inputPtr; + if ((ptr + 3) < _inputEnd) { + byte[] buf = _inputBuffer; +- if ((buf[ptr++] == 'u') ++ if ((buf[ptr++] == 'u') + && (buf[ptr++] == 'l') + && (buf[ptr++] == 'l')) { + int ch = buf[ptr] & 0xFF; +@@ -2977,7 +2983,7 @@ public class UTF8StreamJsonParser + } + _matchToken2("null", 1); + } +- ++ + protected final void _matchToken(String matchStr, int i) throws IOException + { + final int len = matchStr.length(); +@@ -2991,7 +2997,7 @@ public class UTF8StreamJsonParser + } + ++_inputPtr; + } while (++i < len); +- ++ + int ch = _inputBuffer[_inputPtr] & 0xFF; + if (ch >= '0' && ch != ']' && ch != '}') { // expected/allowed chars + _checkMatchEnd(matchStr, i, ch); +@@ -3008,7 +3014,7 @@ public class UTF8StreamJsonParser + } + ++_inputPtr; + } while (++i < len); +- ++ + // but let's also ensure we either get EOF, or non-alphanum char... + if (_inputPtr >= _inputEnd && !_loadMore()) { + return; +@@ -3026,7 +3032,7 @@ public class UTF8StreamJsonParser + _reportInvalidToken(matchStr.substring(0, i)); + } + } +- ++ + /* + /********************************************************** + /* Internal methods, ws skipping, escape/unescape +@@ -3084,7 +3090,7 @@ public class UTF8StreamJsonParser + _throwInvalidSpace(i); + } + } +- } ++ } + throw _constructError("Unexpected end-of-input within/between "+_parsingContext.typeDesc()+" entries"); + } + +@@ -3115,7 +3121,7 @@ public class UTF8StreamJsonParser + _throwInvalidSpace(i); + } + } +- ++ + while (_inputPtr < _inputEnd) { + i = _inputBuffer[_inputPtr++] & 0xFF; + if (i > INT_SPACE) { +@@ -3191,7 +3197,7 @@ public class UTF8StreamJsonParser + if (i == INT_SLASH || i == INT_HASH) { + return _skipColon2(true); + } +- ++_inputPtr; ++ ++_inputPtr; + return i; + } + } +@@ -3374,7 +3380,7 @@ public class UTF8StreamJsonParser + } + } + } +- ++ + @Override + protected char _decodeEscaped() throws IOException + { +@@ -3434,7 +3440,7 @@ public class UTF8StreamJsonParser + int c = firstByte & 0xFF; + if (c > 0x7F) { // if >= 0, is ascii and fine as is + int needed; +- ++ + // Ok; if we end here, we got multi-byte combination + if ((c & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF) + c &= 0x1F; +@@ -3456,7 +3462,7 @@ public class UTF8StreamJsonParser + _reportInvalidOther(d & 0xFF); + } + c = (c << 6) | (d & 0x3F); +- ++ + if (needed > 1) { // needed == 1 means 2 bytes total + d = nextByte(); // 3rd byte + if ((d & 0xC0) != 0x080) { +@@ -3759,9 +3765,9 @@ public class UTF8StreamJsonParser + } + } + int decodedData = bits; +- ++ + // then second base64 char; can't get padding yet, nor ws +- ++ + if (_inputPtr >= _inputEnd) { + _loadMoreGuaranteed(); + } +@@ -3771,7 +3777,7 @@ public class UTF8StreamJsonParser + bits = _decodeBase64Escape(b64variant, ch, 1); + } + decodedData = (decodedData << 6) | bits; +- ++ + // third base64 char; can be padding, but not ws + if (_inputPtr >= _inputEnd) { + _loadMoreGuaranteed(); +@@ -3786,7 +3792,7 @@ public class UTF8StreamJsonParser + if (ch == INT_QUOTE) { + decodedData >>= 4; + builder.append(decodedData); +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + --_inputPtr; // to keep parser state bit more consistent + _handleBase64MissingPadding(b64variant); + } +@@ -3825,7 +3831,7 @@ public class UTF8StreamJsonParser + if (ch == INT_QUOTE) { + decodedData >>= 2; + builder.appendTwoBytes(decodedData); +- if (b64variant.usesPadding()) { ++ if (b64variant.requiresPaddingOnRead()) { + --_inputPtr; // to keep parser state bit more consistent + _handleBase64MissingPadding(b64variant); + } +diff --git a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java +index ae1c2de..05adcf5 100644 +--- a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java ++++ b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java +@@ -4,6 +4,7 @@ import java.io.*; + + import com.fasterxml.jackson.core.*; + import com.fasterxml.jackson.core.base.ParserBase; ++import com.fasterxml.jackson.core.exc.StreamConstraintsException; + import com.fasterxml.jackson.core.io.IOContext; + import com.fasterxml.jackson.core.json.JsonReadContext; + import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer; +@@ -133,7 +134,7 @@ public abstract class NonBlockingJsonParserBase + protected final static int MINOR_COMMENT_C = 53; + protected final static int MINOR_COMMENT_CPP = 54; + protected final static int MINOR_COMMENT_YAML = 55; +- ++ + /* + /********************************************************************** + /* Helper objects, symbols (field names) +@@ -216,7 +217,7 @@ public abstract class NonBlockingJsonParserBase + Double.NaN, + Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, + }; +- ++ + /** + * When tokenizing non-standard ("odd") tokens, this is the type to consider; + * also works as index to actual textual representation. +@@ -235,7 +236,7 @@ public abstract class NonBlockingJsonParserBase + * logical offset within input "stream" + */ + protected int _currBufferStart = 0; +- ++ + /** + * Alternate row tracker, used to keep track of position by `\r` marker + * (whereas _currInputRow tracks `\n`). Used to simplify +@@ -244,7 +245,7 @@ public abstract class NonBlockingJsonParserBase + * case we can simply choose max of two row candidates. + */ + protected int _currInputRowAlt = 1; +- ++ + /* + /********************************************************************** + /* Life-cycle +@@ -372,6 +373,9 @@ public abstract class NonBlockingJsonParserBase + * if no current event (before first call to {@link #nextToken}, or + * after encountering end-of-input), returns null. + * Method can be called for any event. ++ * ++ * @throws IOException if there are general I/O or parse issues, including if the text is too large, ++ * see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} + */ + @Override + public String getText() throws IOException +@@ -382,7 +386,7 @@ public abstract class NonBlockingJsonParserBase + return _getText2(_currToken); + } + +- protected final String _getText2(JsonToken t) ++ protected final String _getText2(JsonToken t) throws IOException + { + if (t == null) { + return null; +@@ -429,7 +433,7 @@ public abstract class NonBlockingJsonParserBase + } + + // // // Let's override default impls for improved performance +- ++ + // @since 2.1 + @Override + public String getValueAsString() throws IOException +@@ -442,7 +446,7 @@ public abstract class NonBlockingJsonParserBase + } + return super.getValueAsString(null); + } +- ++ + // @since 2.1 + @Override + public String getValueAsString(String defValue) throws IOException +@@ -461,7 +465,7 @@ public abstract class NonBlockingJsonParserBase + { + if (_currToken != null) { // null only before/after document + switch (_currToken.id()) { +- ++ + case ID_FIELD_NAME: + if (!_nameCopied) { + String name = _parsingContext.getCurrentName(); +@@ -475,13 +479,13 @@ public abstract class NonBlockingJsonParserBase + _nameCopied = true; + } + return _nameCopyBuffer; +- ++ + case ID_STRING: + // fall through + case ID_NUMBER_INT: + case ID_NUMBER_FLOAT: + return _textBuffer.getTextBuffer(); +- ++ + default: + return _currToken.asCharArray(); + } +@@ -494,7 +498,7 @@ public abstract class NonBlockingJsonParserBase + { + if (_currToken != null) { // null only before/after document + switch (_currToken.id()) { +- ++ + case ID_FIELD_NAME: + return _parsingContext.getCurrentName().length(); + case ID_STRING: +@@ -502,7 +506,7 @@ public abstract class NonBlockingJsonParserBase + case ID_NUMBER_INT: + case ID_NUMBER_FLOAT: + return _textBuffer.size(); +- ++ + default: + return _currToken.asCharArray().length; + } +@@ -576,7 +580,7 @@ public abstract class NonBlockingJsonParserBase + + protected final JsonToken _startArrayScope() throws IOException + { +- _parsingContext = _parsingContext.createChildArrayContext(-1, -1); ++ createChildArrayContext(-1, -1); + _majorState = MAJOR_ARRAY_ELEMENT_FIRST; + _majorStateAfterValue = MAJOR_ARRAY_ELEMENT_NEXT; + return (_currToken = JsonToken.START_ARRAY); +@@ -584,7 +588,7 @@ public abstract class NonBlockingJsonParserBase + + protected final JsonToken _startObjectScope() throws IOException + { +- _parsingContext = _parsingContext.createChildObjectContext(-1, -1); ++ createChildObjectContext(-1, -1); + _majorState = MAJOR_OBJECT_FIELD_FIRST; + _majorStateAfterValue = MAJOR_OBJECT_FIELD_NEXT; + return (_currToken = JsonToken.START_OBJECT); +@@ -636,7 +640,8 @@ public abstract class NonBlockingJsonParserBase + /********************************************************** + */ + +- protected final String _findName(int q1, int lastQuadBytes) throws JsonParseException ++ protected final String _findName(int q1, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + q1 = _padLastQuad(q1, lastQuadBytes); + // Usually we'll find it from the canonical symbol table already +@@ -649,7 +654,8 @@ public abstract class NonBlockingJsonParserBase + return _addName(_quadBuffer, 1, lastQuadBytes); + } + +- protected final String _findName(int q1, int q2, int lastQuadBytes) throws JsonParseException ++ protected final String _findName(int q1, int q2, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + q2 = _padLastQuad(q2, lastQuadBytes); + // Usually we'll find it from the canonical symbol table already +@@ -663,7 +669,8 @@ public abstract class NonBlockingJsonParserBase + return _addName(_quadBuffer, 2, lastQuadBytes); + } + +- protected final String _findName(int q1, int q2, int q3, int lastQuadBytes) throws JsonParseException ++ protected final String _findName(int q1, int q2, int q3, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + q3 = _padLastQuad(q3, lastQuadBytes); + String name = _symbols.findName(q1, q2, q3); +@@ -681,10 +688,11 @@ public abstract class NonBlockingJsonParserBase + // table miss. It needs to demultiplex individual bytes, decode + // multi-byte chars (if any), and then construct Name instance + // and add it to the symbol table. +- protected final String _addName(int[] quads, int qlen, int lastQuadBytes) throws JsonParseException ++ protected final String _addName(int[] quads, int qlen, int lastQuadBytes) ++ throws JsonParseException, StreamConstraintsException + { + /* Ok: must decode UTF-8 chars. No other validation is +- * needed, since unescaping has been done earlier as necessary ++ * needed, since unescaping has been done earlier, as necessary + * (as well as error reporting for unescaped control chars) + */ + // 4 bytes per quad, except last one maybe less +@@ -733,13 +741,13 @@ public abstract class NonBlockingJsonParserBase + if ((ix + needed) > byteLen) { + _reportInvalidEOF(" in field name", JsonToken.FIELD_NAME); + } +- ++ + // Ok, always need at least one more: + int ch2 = quads[ix >> 2]; // current quad, need to shift+mask + byteIx = (ix & 3); + ch2 = (ch2 >> ((3 - byteIx) << 3)); + ++ix; +- ++ + if ((ch2 & 0xC0) != 0x080) { + _reportInvalidOther(ch2); + } +@@ -749,7 +757,7 @@ public abstract class NonBlockingJsonParserBase + byteIx = (ix & 3); + ch2 = (ch2 >> ((3 - byteIx) << 3)); + ++ix; +- ++ + if ((ch2 & 0xC0) != 0x080) { + _reportInvalidOther(ch2); + } +diff --git a/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java b/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java +index 8ca5074..6b98df4 100644 +--- a/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java ++++ b/src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java +@@ -7,6 +7,7 @@ import java.math.BigDecimal; + import java.math.BigInteger; + + import com.fasterxml.jackson.core.*; ++import com.fasterxml.jackson.core.async.NonBlockingInputFeeder; + + /** + * Helper class that implements +@@ -46,7 +47,7 @@ public class JsonParserDelegate extends JsonParser + delegate.disable(f); + return this; + } +- ++ + @Override public boolean isEnabled(Feature f) { return delegate.isEnabled(f); } + @Override public int getFeatureMask() { return delegate.getFeatureMask(); } + +@@ -75,15 +76,42 @@ public class JsonParserDelegate extends JsonParser + @Override public Version version() { return delegate.version(); } + @Override public Object getInputSource() { return delegate.getInputSource(); } + ++ /* ++ /********************************************************************** ++ /* Constraints violation checking (2.15) ++ /********************************************************************** ++ */ ++ ++ @Override ++ public StreamReadConstraints streamReadConstraints() { ++ return delegate.streamReadConstraints(); ++ } ++ + /* + /********************************************************************** + /* Capability introspection + /********************************************************************** + */ + +- @Override public boolean requiresCustomCodec() { return delegate.requiresCustomCodec(); } ++ @Override ++ public boolean canParseAsync() { ++ return delegate.canParseAsync(); ++ } ++ ++ @Override ++ public NonBlockingInputFeeder getNonBlockingInputFeeder() { ++ return delegate.getNonBlockingInputFeeder(); ++ } ++ ++ @Override ++ public JacksonFeatureSet getReadCapabilities() { ++ return delegate.getReadCapabilities(); ++ } + +- @Override public JacksonFeatureSet getReadCapabilities() { return delegate.getReadCapabilities(); } ++ @Override ++ public boolean requiresCustomCodec() { ++ return delegate.requiresCustomCodec(); ++ } + + /* + /********************************************************************** +@@ -178,13 +206,13 @@ public class JsonParserDelegate extends JsonParser + /* Public API, access to token numeric values + /********************************************************************** + */ +- ++ + @Override + public BigInteger getBigIntegerValue() throws IOException { return delegate.getBigIntegerValue(); } + + @Override + public boolean getBooleanValue() throws IOException { return delegate.getBooleanValue(); } +- ++ + @Override + public byte getByteValue() throws IOException { return delegate.getByteValue(); } + +@@ -215,6 +243,9 @@ public class JsonParserDelegate extends JsonParser + @Override + public Number getNumberValueExact() throws IOException { return delegate.getNumberValueExact(); } + ++ @Override ++ public Object getNumberValueDeferred() throws IOException { return delegate.getNumberValueDeferred(); } ++ + /* + /********************************************************************** + /* Public API, access to token information, coercion/conversion +@@ -247,7 +278,7 @@ public class JsonParserDelegate extends JsonParser + @Override public JsonToken nextValue() throws IOException { return delegate.nextValue(); } + + @Override public void finishToken() throws IOException { delegate.finishToken(); } +- ++ + @Override + public JsonParser skipChildren() throws IOException { + delegate.skipChildren(); +diff --git a/src/main/java/com/fasterxml/jackson/core/util/ReadConstrainedTextBuffer.java b/src/main/java/com/fasterxml/jackson/core/util/ReadConstrainedTextBuffer.java +new file mode 100644 +index 0000000..40f9825 +--- /dev/null ++++ b/src/main/java/com/fasterxml/jackson/core/util/ReadConstrainedTextBuffer.java +@@ -0,0 +1,29 @@ ++package com.fasterxml.jackson.core.util; ++ ++import com.fasterxml.jackson.core.StreamReadConstraints; ++import com.fasterxml.jackson.core.exc.StreamConstraintsException; ++ ++public final class ReadConstrainedTextBuffer extends TextBuffer { ++ ++ private final StreamReadConstraints _streamReadConstraints; ++ ++ public ReadConstrainedTextBuffer(StreamReadConstraints streamReadConstraints, BufferRecycler bufferRecycler) { ++ super(bufferRecycler); ++ _streamReadConstraints = streamReadConstraints; ++ } ++ ++ /* ++ /********************************************************************** ++ /* Convenience methods for validation ++ /********************************************************************** ++ */ ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ protected void validateStringLength(int length) throws StreamConstraintsException ++ { ++ _streamReadConstraints.validateStringLength(length); ++ } ++} +diff --git a/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java b/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java +index c1b1a9c..a021fc5 100644 +--- a/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java ++++ b/src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java +@@ -4,6 +4,7 @@ import java.io.*; + import java.math.BigDecimal; + import java.util.*; + ++import com.fasterxml.jackson.core.JsonParseException; + import com.fasterxml.jackson.core.io.NumberInput; + + /** +@@ -24,7 +25,7 @@ import com.fasterxml.jackson.core.io.NumberInput; + * + * + */ +-public final class TextBuffer ++public class TextBuffer + { + final static char[] NO_CHARS = new char[0]; + +@@ -40,7 +41,7 @@ public final class TextBuffer + * For 2.10, let's limit to using 64kc chunks (128 kB) -- was 256kC/512kB up to 2.9 + */ + final static int MAX_SEGMENT_LEN = 0x10000; +- ++ + /* + /********************************************************** + /* Configuration: +@@ -120,13 +121,13 @@ public final class TextBuffer + /********************************************************** + */ + +- public TextBuffer(BufferRecycler allocator) { ++ public TextBuffer( BufferRecycler allocator) { + _allocator = allocator; + } + + // @since 2.10 + protected TextBuffer(BufferRecycler allocator, char[] initialSegment) { +- _allocator = allocator; ++ this(allocator); + _currentSegment = initialSegment; + _currentSize = initialSegment.length; + _inputStart = -1; +@@ -230,7 +231,7 @@ public final class TextBuffer + _currentSegment[0] = ch; // lgtm [java/dereferenced-value-may-be-null] + _currentSize = _segmentSize = 1; + } +- ++ + /** + * Method called to initialize the buffer with a shared copy of data; + * this means that buffer will just have pointers to actual data. It +@@ -258,7 +259,13 @@ public final class TextBuffer + } + } + +- public void resetWithCopy(char[] buf, int offset, int len) ++ /** ++ * @param buf Buffer that contains new contents ++ * @param offset Offset of the first content character in {@code buf} ++ * @param len Length of content in {@code buf} ++ * @throws IOException if the buffer has grown too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} ++ */ ++ public void resetWithCopy(char[] buf, int offset, int len) throws IOException + { + _inputBuffer = null; + _inputStart = -1; // indicates shared buffer not used +@@ -277,8 +284,14 @@ public final class TextBuffer + append(buf, offset, len); + } + +- // @since 2.9 +- public void resetWithCopy(String text, int start, int len) ++ /** ++ * @param text String that contains new contents ++ * @param start Offset of the first content character in {@code text} ++ * @param len Length of content in {@code text} ++ * @throws IOException if the buffer has grown too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} ++ * @since 2.9 ++ */ ++ public void resetWithCopy(String text, int start, int len) throws IOException + { + _inputBuffer = null; + _inputStart = -1; +@@ -296,12 +309,17 @@ public final class TextBuffer + append(text, start, len); + } + +- public void resetWithString(String value) ++ /** ++ * @param value to replace existing buffer ++ * @throws IOException if the value is too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} ++ */ ++ public void resetWithString(String value) throws IOException + { + _inputBuffer = null; + _inputStart = -1; + _inputLen = 0; + ++ validateStringLength(value.length()); + _resultString = value; + _resultArray = null; + +@@ -309,7 +327,7 @@ public final class TextBuffer + clearSegments(); + } + _currentSize = 0; +- ++ + } + + /** +@@ -401,8 +419,9 @@ public final class TextBuffer + * fashion or not: this typically require allocation of the result buffer. + * + * @return Aggregated {@code char[]} that contains all buffered content ++ * @throws IOException if the text is too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} + */ +- public char[] getTextBuffer() ++ public char[] getTextBuffer() throws IOException + { + // Are we just using shared input buffer? + if (_inputStart >= 0) return _inputBuffer; +@@ -430,12 +449,14 @@ public final class TextBuffer + * fashion or not: this typically require construction of the result String. + * + * @return Aggregated buffered contents as a {@link java.lang.String} ++ * @throws IOException if the contents are too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} + */ +- public String contentsAsString() ++ public String contentsAsString() throws IOException + { + if (_resultString == null) { + // Has array been requested? Can make a shortcut, if so: + if (_resultArray != null) { ++ // _resultArray length should already be validated, no need to check again + _resultString = new String(_resultArray); + } else { + // Do we use shared array? +@@ -443,16 +464,28 @@ public final class TextBuffer + if (_inputLen < 1) { + return (_resultString = ""); + } ++ validateStringLength(_inputLen); + _resultString = new String(_inputBuffer, _inputStart, _inputLen); + } else { // nope... need to copy + // But first, let's see if we have just one buffer + int segLen = _segmentSize; + int currLen = _currentSize; +- ++ + if (segLen == 0) { // yup +- _resultString = (currLen == 0) ? "" : new String(_currentSegment, 0, currLen); ++ if (currLen == 0) { ++ _resultString = ""; ++ } else { ++ validateStringLength(currLen); ++ _resultString = new String(_currentSegment, 0, currLen); ++ } + } else { // no, need to combine +- StringBuilder sb = new StringBuilder(segLen + currLen); ++ final int builderLen = segLen + currLen; ++ if (builderLen < 0) { ++ _reportBufferOverflow(segLen, currLen); ++ } ++ validateStringLength(builderLen); ++ StringBuilder sb = new StringBuilder(builderLen); ++ + // First stored segments + if (_segments != null) { + for (int i = 0, len = _segments.size(); i < len; ++i) { +@@ -470,8 +503,12 @@ public final class TextBuffer + + return _resultString; + } +- +- public char[] contentsAsArray() { ++ ++ /** ++ * @return char array ++ * @throws IOException if the text is too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} ++ */ ++ public char[] contentsAsArray() throws IOException { + char[] result = _resultArray; + if (result == null) { + _resultArray = result = resultArray(); +@@ -481,28 +518,22 @@ public final class TextBuffer + + /** + * Convenience method for converting contents of the buffer +- * into a {@link BigDecimal}. ++ * into a Double value. + * +- * @return Buffered text value parsed as a {@link BigDecimal}, if possible ++ * @param useFastParser whether to use {@code FastDoubleParser} ++ * @return Buffered text value parsed as a {@link Double}, if possible + * + * @throws NumberFormatException if contents are not a valid Java number ++ * ++ * @since 2.14 + */ +- public BigDecimal contentsAsDecimal() throws NumberFormatException +- { +- // Already got a pre-cut array? +- if (_resultArray != null) { +- return NumberInput.parseBigDecimal(_resultArray); +- } +- // Or a shared buffer? +- if ((_inputStart >= 0) && (_inputBuffer != null)) { +- return NumberInput.parseBigDecimal(_inputBuffer, _inputStart, _inputLen); +- } +- // Or if not, just a single buffer (the usual case) +- if ((_segmentSize == 0) && (_currentSegment != null)) { +- return NumberInput.parseBigDecimal(_currentSegment, 0, _currentSize); ++ public double contentsAsDouble(final boolean useFastParser) throws NumberFormatException { ++ try { ++ return NumberInput.parseDouble(contentsAsString(), useFastParser); ++ } catch (IOException e) { ++ // JsonParseException is used to denote a string that is too long ++ throw new NumberFormatException(e.getMessage()); + } +- // If not, let's just get it aggregated... +- return NumberInput.parseBigDecimal(contentsAsArray()); + } + + /** +@@ -512,27 +543,14 @@ public final class TextBuffer + * @return Buffered text value parsed as a {@link Double}, if possible + * + * @throws NumberFormatException if contents are not a valid Java number ++ * + * @deprecated use {@link #contentsAsDouble(boolean)} + */ +- @Deprecated ++ @Deprecated // @since 2.14 + public double contentsAsDouble() throws NumberFormatException { + return contentsAsDouble(false); + } + +- /** +- * Convenience method for converting contents of the buffer +- * into a Double value. +- * +- * @param useFastParser whether to use {@link com.fasterxml.jackson.core.io.doubleparser} +- * @return Buffered text value parsed as a {@link Double}, if possible +- * +- * @throws NumberFormatException if contents are not a valid Java number +- * @since 2.14 +- */ +- public double contentsAsDouble(final boolean useFastParser) throws NumberFormatException { +- return NumberInput.parseDouble(contentsAsString(), useFastParser); +- } +- + /** + * Convenience method for converting contents of the buffer + * into a Float value. +@@ -540,10 +558,9 @@ public final class TextBuffer + * @return Buffered text value parsed as a {@link Float}, if possible + * + * @throws NumberFormatException if contents are not a valid Java number +- * @since 2.14 + * @deprecated use {@link #contentsAsFloat(boolean)} + */ +- @Deprecated ++ @Deprecated // @since 2.14 + public float contentsAsFloat() throws NumberFormatException { + return contentsAsFloat(false); + } +@@ -552,14 +569,38 @@ public final class TextBuffer + * Convenience method for converting contents of the buffer + * into a Float value. + * +- * @param useFastParser whether to use {@link com.fasterxml.jackson.core.io.doubleparser} ++ * @param useFastParser whether to use {@code FastDoubleParser} + * @return Buffered text value parsed as a {@link Float}, if possible + * + * @throws NumberFormatException if contents are not a valid Java number + * @since 2.14 + */ + public float contentsAsFloat(final boolean useFastParser) throws NumberFormatException { +- return NumberInput.parseFloat(contentsAsString(), useFastParser); ++ try { ++ return NumberInput.parseFloat(contentsAsString(), useFastParser); ++ } catch (IOException e) { ++ // JsonParseException is used to denote a string that is too long ++ throw new NumberFormatException(e.getMessage()); ++ } ++ } ++ ++ /** ++ * @return Buffered text value parsed as a {@link BigDecimal}, if possible ++ * @throws NumberFormatException if contents are not a valid Java number ++ * ++ * @deprecated Since 2.15 just access String contents if necessary, call ++ * {@link NumberInput#parseBigDecimal(String, boolean)} (or other overloads) ++ * directly instead ++ */ ++ @Deprecated ++ public BigDecimal contentsAsDecimal() throws NumberFormatException { ++ // Was more optimized earlier, removing special handling due to deprecation ++ try { ++ return NumberInput.parseBigDecimal(contentsAsArray()); ++ } catch (IOException e) { ++ // JsonParseException is used to denote a string that is too long ++ throw new NumberFormatException(e.getMessage()); ++ } + } + + /** +@@ -653,14 +694,14 @@ public final class TextBuffer + for (int i = 0, end = _segments.size(); i < end; ++i) { + char[] curr = _segments.get(i); + int currLen = curr.length; +- w.write(curr, 0, currLen); + total += currLen; ++ w.write(curr, 0, currLen); + } + } + int len = _currentSize; + if (len > 0) { +- w.write(_currentSegment, 0, len); + total += len; ++ w.write(_currentSegment, 0, len); + } + return total; + } +@@ -681,23 +722,35 @@ public final class TextBuffer + } + } + +- public void append(char c) { ++ /** ++ * @param c char to append ++ * @throws IOException if the buffer has grown too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} ++ */ ++ public void append(char c) throws IOException { + // Using shared buffer so far? + if (_inputStart >= 0) { + unshare(16); + } + _resultString = null; + _resultArray = null; ++ + // Room in current segment? + char[] curr = _currentSegment; + if (_currentSize >= curr.length) { +- expand(1); ++ validateAppend(1); ++ expand(); + curr = _currentSegment; + } + curr[_currentSize++] = c; + } + +- public void append(char[] c, int start, int len) ++ /** ++ * @param c char array to append ++ * @param start the start index within the array (from which we read chars to append) ++ * @param len number of chars to take from the array ++ * @throws IOException if the buffer has grown too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} ++ */ ++ public void append(char[] c, int start, int len) throws IOException + { + // Can't append to shared buf (sanity check) + if (_inputStart >= 0) { +@@ -709,12 +762,15 @@ public final class TextBuffer + // Room in current segment? + char[] curr = _currentSegment; + int max = curr.length - _currentSize; +- ++ + if (max >= len) { + System.arraycopy(c, start, curr, _currentSize, len); + _currentSize += len; + return; + } ++ ++ validateAppend(len); ++ + // No room for all, need to copy part(s): + if (max > 0) { + System.arraycopy(c, start, curr, _currentSize, max); +@@ -724,7 +780,7 @@ public final class TextBuffer + // And then allocate new segment; we are guaranteed to now + // have enough room in segment. + do { +- expand(len); ++ expand(); + int amount = Math.min(_currentSegment.length, len); + System.arraycopy(c, start, _currentSegment, 0, amount); + _currentSize += amount; +@@ -733,7 +789,13 @@ public final class TextBuffer + } while (len > 0); + } + +- public void append(String str, int offset, int len) ++ /** ++ * @param str string to append ++ * @param offset the start index within the string (from which we read chars to append) ++ * @param len number of chars to take from the string ++ * @throws IOException if the buffer has grown too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} ++ */ ++ public void append(String str, int offset, int len) throws IOException + { + // Can't append to shared buf (sanity check) + if (_inputStart >= 0) { +@@ -750,6 +812,9 @@ public final class TextBuffer + _currentSize += len; + return; + } ++ ++ validateAppend(len); ++ + // No room for all, need to copy part(s): + if (max > 0) { + str.getChars(offset, offset+max, curr, _currentSize); +@@ -759,7 +824,7 @@ public final class TextBuffer + // And then allocate new segment; we are guaranteed to now + // have enough room in segment. + do { +- expand(len); ++ expand(); + int amount = Math.min(_currentSegment.length, len); + str.getChars(offset, offset+amount, _currentSegment, 0); + _currentSize += amount; +@@ -768,6 +833,15 @@ public final class TextBuffer + } while (len > 0); + } + ++ private void validateAppend(int toAppend) throws IOException { ++ int newTotalLength = _segmentSize + _currentSize + toAppend; ++ // guard against overflow ++ if (newTotalLength < 0) { ++ newTotalLength = Integer.MAX_VALUE; ++ } ++ validateStringLength(newTotalLength); ++ } ++ + /* + /********************************************************** + /* Raw access, for high-performance use: +@@ -788,7 +862,7 @@ public final class TextBuffer + _currentSegment = buf(0); + } else if (_currentSize >= curr.length) { + // Plus, we better have room for at least one more char +- expand(1); ++ expand(); + } + } + return _currentSegment; +@@ -828,10 +902,11 @@ public final class TextBuffer + * @param len Length of content (in characters) of the current active segment + * + * @return String that contains all buffered content ++ * @throws IOException if the text is too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} + * + * @since 2.6 + */ +- public String setCurrentAndReturn(int len) { ++ public String setCurrentAndReturn(int len) throws IOException { + _currentSize = len; + // We can simplify handling here compared to full `contentsAsString()`: + if (_segmentSize > 0) { // longer text; call main method +@@ -839,12 +914,17 @@ public final class TextBuffer + } + // more common case: single segment + int currLen = _currentSize; ++ validateStringLength(currLen); + String str = (currLen == 0) ? "" : new String(_currentSegment, 0, currLen); + _resultString = str; + return str; + } +- +- public char[] finishCurrentSegment() { ++ ++ /** ++ * @return char array ++ * @throws IOException if the text is too large, see {@link com.fasterxml.jackson.core.StreamReadConstraints.Builder#maxStringLength(int)} ++ */ ++ public char[] finishCurrentSegment() throws IOException { + if (_segments == null) { + _segments = new ArrayList(); + } +@@ -852,7 +932,11 @@ public final class TextBuffer + _segments.add(_currentSegment); + int oldLen = _currentSegment.length; + _segmentSize += oldLen; ++ if (_segmentSize < 0) { ++ _reportBufferOverflow(_segmentSize - oldLen, oldLen); ++ } + _currentSize = 0; ++ validateStringLength(_segmentSize); + + // Let's grow segments by 50% + int newLen = oldLen + (oldLen >> 1); +@@ -866,6 +950,51 @@ public final class TextBuffer + return curr; + } + ++ /** ++ * @param lastSegmentEnd End offset in the currently active segment, ++ * could be 0 in the case of first character is ++ * delimiter or end-of-line ++ * @param trimTrailingSpaces Whether trailing spaces should be trimmed or not ++ * @return token as text ++ * @since 2.15 ++ */ ++ public String finishAndReturn(int lastSegmentEnd, boolean trimTrailingSpaces) throws IOException ++ { ++ if (trimTrailingSpaces) { ++ // First, see if it's enough to trim end of current segment: ++ int ptr = lastSegmentEnd - 1; ++ if (ptr < 0 || _currentSegment[ptr] <= 0x0020) { ++ return _doTrim(ptr); ++ } ++ } ++ _currentSize = lastSegmentEnd; ++ return contentsAsString(); ++ } ++ ++ // @since 2.15 ++ private String _doTrim(int ptr) throws IOException ++ { ++ while (true) { ++ final char[] curr = _currentSegment; ++ while (--ptr >= 0) { ++ if (curr[ptr] > 0x0020) { // found the ending non-space char, all done: ++ _currentSize = ptr+1; ++ return contentsAsString(); ++ } ++ } ++ // nope: need to handle previous segment; if there is one: ++ if (_segments == null || _segments.isEmpty()) { ++ break; ++ } ++ _currentSegment = _segments.remove(_segments.size() - 1); ++ ptr = _currentSegment.length; ++ } ++ // we get here if everything was trimmed, so: ++ _currentSize = 0; ++ _hasSegments = false; ++ return contentsAsString(); ++ } ++ + /** + * Method called to expand size of the current segment, to + * accommodate for more contiguous content. Usually only +@@ -891,7 +1020,7 @@ public final class TextBuffer + * Method called to expand size of the current segment, to + * accommodate for more contiguous content. Usually only + * used when parsing tokens like names if even then. +- * ++ * + * @param minSize Required minimum strength of the current segment + * + * @return Expanded current segment +@@ -916,7 +1045,13 @@ public final class TextBuffer + * {@link #contentsAsString}, since it's not guaranteed that resulting + * String is cached. + */ +- @Override public String toString() { return contentsAsString(); } ++ @Override public String toString() { ++ try { ++ return contentsAsString(); ++ } catch (IOException e) { ++ return "TextBuffer: Exception when reading contents"; ++ } ++ } + + /* + /********************************************************** +@@ -950,7 +1085,7 @@ public final class TextBuffer + } + + // Method called when current segment is full, to allocate new segment. +- private void expand(int minNewSegmentSize) ++ private void expand() + { + // First, let's move current segment to segment list: + if (_segments == null) { +@@ -960,9 +1095,12 @@ public final class TextBuffer + _hasSegments = true; + _segments.add(curr); + _segmentSize += curr.length; ++ if (_segmentSize < 0) { ++ _reportBufferOverflow(_segmentSize - curr.length, curr.length); ++ } + _currentSize = 0; + int oldLen = curr.length; +- ++ + // Let's grow segments by 50% minimum + int newLen = oldLen + (oldLen >> 1); + if (newLen < MIN_SEGMENT_LEN) { +@@ -973,7 +1111,7 @@ public final class TextBuffer + _currentSegment = carr(newLen); + } + +- private char[] resultArray() ++ private char[] resultArray() throws IOException + { + if (_resultString != null) { // Can take a shortcut... + return _resultString.toCharArray(); +@@ -984,6 +1122,7 @@ public final class TextBuffer + if (len < 1) { + return NO_CHARS; + } ++ validateStringLength(len); + final int start = _inputStart; + if (start == 0) { + return Arrays.copyOf(_inputBuffer, len); +@@ -993,8 +1132,12 @@ public final class TextBuffer + // nope, not shared + int size = size(); + if (size < 1) { ++ if (size < 0) { ++ _reportBufferOverflow(_segmentSize, _currentSize); ++ } + return NO_CHARS; + } ++ validateStringLength(size); + int offset = 0; + final char[] result = carr(size); + if (_segments != null) { +@@ -1010,4 +1153,33 @@ public final class TextBuffer + } + + private char[] carr(int len) { return new char[len]; } ++ ++ /* ++ /********************************************************************** ++ /* Convenience methods for validation ++ /********************************************************************** ++ */ ++ ++ protected void _reportBufferOverflow(int prev, int curr) { ++ long newSize = (long) prev + (long) curr; ++ throw new IllegalStateException("TextBuffer overrun: size reached (" ++ +newSize+") exceeds maximum of "+Integer.MAX_VALUE); ++ } ++ ++ /** ++ * Convenience method that can be used to verify that a String ++ * of specified length does not exceed maximum specific by this ++ * constraints object: if it does, a ++ * {@link JsonParseException} ++ * is thrown. ++ * ++ * @param length Length of string in input units ++ * ++ * @throws IOException If length exceeds maximum ++ * @since 2.15 ++ */ ++ protected void validateStringLength(int length) throws IOException ++ { ++ // no-op ++ } + } +diff --git a/src/test/java/com/fasterxml/jackson/core/fuzz/Fuzz34435ParseTest.java b/src/test/java/com/fasterxml/jackson/core/fuzz/Fuzz34435ParseTest.java +index e83cf3d..d3689a5 100644 +--- a/src/test/java/com/fasterxml/jackson/core/fuzz/Fuzz34435ParseTest.java ++++ b/src/test/java/com/fasterxml/jackson/core/fuzz/Fuzz34435ParseTest.java +@@ -17,6 +17,7 @@ public class Fuzz34435ParseTest extends BaseTest + .enable(JsonReadFeature.ALLOW_YAML_COMMENTS) + .enable(JsonReadFeature.ALLOW_SINGLE_QUOTES) + .enable(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES) ++ .streamReadConstraints(StreamReadConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + + JsonParser p = f.createParser(/*ObjectReadContext.empty(), */ DOC); +diff --git a/src/test/java/com/fasterxml/jackson/core/jsonptr/JsonPointerOOME736Test.java b/src/test/java/com/fasterxml/jackson/core/jsonptr/JsonPointerOOME736Test.java +index b4547bc..d2f4e0b 100644 +--- a/src/test/java/com/fasterxml/jackson/core/jsonptr/JsonPointerOOME736Test.java ++++ b/src/test/java/com/fasterxml/jackson/core/jsonptr/JsonPointerOOME736Test.java +@@ -10,7 +10,10 @@ public class JsonPointerOOME736Test extends BaseTest + int MAX_DEPTH = 120_000; + // Create nesting of 120k arrays + String INPUT = new String(new char[MAX_DEPTH]).replace("\0", "["); +- JsonParser parser = createParser(MODE_READER, INPUT); ++ final JsonFactory f = JsonFactory.builder() ++ .streamReadConstraints(StreamReadConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) ++ .build(); ++ JsonParser parser = createParser(f, MODE_READER, INPUT); + try { + while (true) { + parser.nextToken(); +diff --git a/src/test/java/com/fasterxml/jackson/core/read/ArrayParsingTest.java b/src/test/java/com/fasterxml/jackson/core/read/ArrayParsingTest.java +index cb52d2a..842ec63 100644 +--- a/src/test/java/com/fasterxml/jackson/core/read/ArrayParsingTest.java ++++ b/src/test/java/com/fasterxml/jackson/core/read/ArrayParsingTest.java +@@ -1,6 +1,7 @@ + package com.fasterxml.jackson.core.read; + + import com.fasterxml.jackson.core.*; ++import com.fasterxml.jackson.core.exc.StreamConstraintsException; + import com.fasterxml.jackson.core.json.JsonReadFeature; + + /** +@@ -105,7 +106,21 @@ public class ArrayParsingTest + _testNotMissingValueByEnablingFeature(true); + _testNotMissingValueByEnablingFeature(false); + } +- ++ ++ public void testDeepNesting() throws Exception ++ { ++ final String DOC = createDeepNestedDoc(1050); ++ try (JsonParser jp = createParserUsingStream(new JsonFactory(), DOC, "UTF-8")) { ++ JsonToken jt; ++ while ((jt = jp.nextToken()) != null) { ++ ++ } ++ fail("expected StreamConstraintsException"); ++ } catch (StreamConstraintsException e) { ++ assertEquals("Depth (1001) exceeds the maximum allowed nesting depth (1000)", e.getMessage()); ++ } ++ } ++ + private void _testMissingValueByEnablingFeature(boolean useStream) throws Exception { + String DOC = "[ \"a\",,,,\"abc\", ] "; + +@@ -182,4 +197,18 @@ public class ArrayParsingTest + + jp.close(); + } ++ ++ private String createDeepNestedDoc(final int depth) { ++ StringBuilder sb = new StringBuilder(); ++ sb.append("["); ++ for (int i = 0; i < depth; i++) { ++ sb.append("{ \"a\": ["); ++ } ++ sb.append(" \"val\" "); ++ for (int i = 0; i < depth; i++) { ++ sb.append("]}"); ++ } ++ sb.append("]"); ++ return sb.toString(); ++ } + } +diff --git a/src/test/java/com/fasterxml/jackson/core/read/NumberOverflowTest.java b/src/test/java/com/fasterxml/jackson/core/read/NumberOverflowTest.java +index 8de42c9..47e72f0 100644 +--- a/src/test/java/com/fasterxml/jackson/core/read/NumberOverflowTest.java ++++ b/src/test/java/com/fasterxml/jackson/core/read/NumberOverflowTest.java +@@ -8,9 +8,12 @@ import com.fasterxml.jackson.core.exc.InputCoercionException; + public class NumberOverflowTest + extends com.fasterxml.jackson.core.BaseTest + { +- private final JsonFactory FACTORY = new JsonFactory(); ++ private final JsonFactory FACTORY = JsonFactory.builder() ++ .streamReadConstraints(StreamReadConstraints.builder().maxNumberLength(1000000).build()) ++ .build(); + + // NOTE: this should be long enough to trigger perf problems ++ // 19- + private final static int BIG_NUM_LEN = 199999; + private final static String BIG_POS_INTEGER; + static { +@@ -65,7 +68,7 @@ public class NumberOverflowTest + { + for (int mode : ALL_STREAMING_MODES) { + for (String doc : new String[] { BIG_POS_DOC, BIG_NEG_DOC }) { +- JsonParser p = createParser(mode, doc); ++ JsonParser p = createParser(FACTORY, mode, doc); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + try { +@@ -78,14 +81,14 @@ public class NumberOverflowTest + p.close(); + } + } +- } ++ } + + // [jackson-core#488] + public void testMaliciousIntOverflow() throws Exception + { + for (int mode : ALL_STREAMING_MODES) { + for (String doc : new String[] { BIG_POS_DOC, BIG_NEG_DOC }) { +- JsonParser p = createParser(mode, doc); ++ JsonParser p = createParser(FACTORY, mode, doc); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + try { +@@ -98,14 +101,14 @@ public class NumberOverflowTest + p.close(); + } + } +- } ++ } + + // [jackson-core#488] + public void testMaliciousBigIntToDouble() throws Exception + { + for (int mode : ALL_STREAMING_MODES) { + final String doc = BIG_POS_DOC; +- JsonParser p = createParser(mode, doc); ++ JsonParser p = createParser(FACTORY, mode, doc); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + double d = p.getDoubleValue(); +@@ -113,14 +116,14 @@ public class NumberOverflowTest + assertToken(JsonToken.END_ARRAY, p.nextToken()); + p.close(); + } +- } ++ } + + // [jackson-core#488] + public void testMaliciousBigIntToFloat() throws Exception + { + for (int mode : ALL_STREAMING_MODES) { + final String doc = BIG_POS_DOC; +- JsonParser p = createParser(mode, doc); ++ JsonParser p = createParser(FACTORY, mode, doc); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + float f = p.getFloatValue(); +diff --git a/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java b/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java +index 912720e..8b92c75 100644 +--- a/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java ++++ b/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java +@@ -40,7 +40,7 @@ public class NumberParsingTest + + private void _testSimpleBoolean(int mode) throws Exception + { +- JsonParser p = createParser(mode, "[ true ]"); ++ JsonParser p = createParser(jsonFactory(), mode, "[ true ]"); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_TRUE, p.nextToken()); + assertEquals(true, p.getBooleanValue()); +@@ -67,7 +67,7 @@ public class NumberParsingTest + private void _testSimpleInt(int EXP_I, int mode) throws Exception + { + String DOC = "[ "+EXP_I+" ]"; +- JsonParser p = createParser(mode, DOC); ++ JsonParser p = createParser(jsonFactory(), mode, DOC); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(JsonParser.NumberType.INT, p.getNumberType()); +@@ -109,7 +109,7 @@ public class NumberParsingTest + p.close(); + + DOC = String.valueOf(EXP_I); +- p = createParser(mode, DOC + " "); // DataInput requires separator ++ p = createParser(jsonFactory(), mode, DOC + " "); // DataInput requires separator + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertTrue(p.isExpectedNumberIntToken()); + assertEquals(DOC, p.getText()); +@@ -124,13 +124,13 @@ public class NumberParsingTest + + // and finally, coercion from double to int; couple of variants + DOC = String.valueOf(EXP_I)+".0"; +- p = createParser(mode, DOC + " "); ++ p = createParser(jsonFactory(), mode, DOC + " "); + assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); + assertFalse(p.isExpectedNumberIntToken()); + assertEquals(EXP_I, p.getValueAsInt()); + p.close(); + +- p = createParser(mode, DOC + " "); ++ p = createParser(jsonFactory(), mode, DOC + " "); + assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); + assertEquals(EXP_I, p.getValueAsInt(0)); + p.close(); +@@ -141,12 +141,12 @@ public class NumberParsingTest + // let's test with readers and streams, separate code paths: + for (int mode : ALL_MODES) { + String DOC = "[ "+Integer.MAX_VALUE+","+Integer.MIN_VALUE+" ]"; +- JsonParser p = createParser(mode, DOC); ++ JsonParser p = createParser(jsonFactory(), mode, DOC); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(JsonParser.NumberType.INT, p.getNumberType()); + assertEquals(Integer.MAX_VALUE, p.getIntValue()); +- ++ + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(JsonParser.NumberType.INT, p.getNumberType()); + assertEquals(Integer.MIN_VALUE, p.getIntValue()); +@@ -204,12 +204,48 @@ public class NumberParsingTest + assertEquals(-131, NumberInput.parseLong("-131")); + assertEquals(2709, NumberInput.parseLong("2709")); + assertEquals(-9999, NumberInput.parseLong("-9999")); ++ assertEquals(1234567890123456789L, NumberInput.parseLong("1234567890123456789")); ++ assertEquals(-1234567890123456789L, NumberInput.parseLong("-1234567890123456789")); + assertEquals(Long.MIN_VALUE, NumberInput.parseLong(""+Long.MIN_VALUE)); + assertEquals(Integer.MIN_VALUE-1, NumberInput.parseLong(""+(Integer.MIN_VALUE-1))); + assertEquals(Long.MAX_VALUE, NumberInput.parseLong(""+Long.MAX_VALUE)); + assertEquals(Integer.MAX_VALUE+1, NumberInput.parseLong(""+(Integer.MAX_VALUE+1))); + } + ++ public void testIntOverflow() { ++ try { ++ // Integer.MAX_VALUE + 1 ++ NumberInput.parseInt("2147483648"); ++ fail("expected NumberFormatException"); ++ } catch (NumberFormatException nfe) { ++ verifyException(nfe, "For input string: \"2147483648\""); ++ } ++ try { ++ // Integer.MIN_VALUE - 1 ++ NumberInput.parseInt("-2147483649"); ++ fail("expected NumberFormatException"); ++ } catch (NumberFormatException nfe) { ++ verifyException(nfe, "For input string: \"-2147483649\""); ++ } ++ } ++ ++ public void testLongOverflow() { ++ try { ++ // Long.MAX_VALUE + 1 ++ NumberInput.parseLong("9223372036854775808"); ++ fail("expected NumberFormatException"); ++ } catch (NumberFormatException nfe) { ++ verifyException(nfe, "For input string: \"9223372036854775808\""); ++ } ++ try { ++ // Long.MIN_VALUE - 1 ++ NumberInput.parseLong("-9223372036854775809"); ++ fail("expected NumberFormatException"); ++ } catch (NumberFormatException nfe) { ++ verifyException(nfe, "For input string: \"-9223372036854775809\""); ++ } ++ } ++ + // Found by oss-fuzzer + public void testVeryLongIntRootValue() throws Exception + { +@@ -251,8 +287,8 @@ public class NumberParsingTest + private void _testSimpleLong(int mode) throws Exception + { + long EXP_L = 12345678907L; +- +- JsonParser p = createParser(mode, "[ "+EXP_L+" ]"); ++ ++ JsonParser p = createParser(jsonFactory(), mode, "[ "+EXP_L+" ]"); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + // beyond int, should be long +@@ -260,7 +296,7 @@ public class NumberParsingTest + assertEquals(""+EXP_L, p.getText()); + + assertEquals(EXP_L, p.getLongValue()); +- // Should get an exception if trying to convert to int ++ // Should get an exception if trying to convert to int + try { + p.getIntValue(); + } catch (InputCoercionException e) { +@@ -278,13 +314,15 @@ public class NumberParsingTest + for (int mode : ALL_MODES) { + long belowMinInt = -1L + Integer.MIN_VALUE; + long aboveMaxInt = 1L + Integer.MAX_VALUE; +- String input = "[ "+Long.MAX_VALUE+","+Long.MIN_VALUE+","+aboveMaxInt+", "+belowMinInt+" ]"; +- JsonParser p = createParser(mode, input); ++ long belowMaxLong = -1L + Long.MAX_VALUE; ++ long aboveMinLong = 1L + Long.MIN_VALUE; ++ String input = "[ "+Long.MAX_VALUE+","+Long.MIN_VALUE+","+aboveMaxInt+", "+belowMinInt+","+belowMaxLong+", "+aboveMinLong+" ]"; ++ JsonParser p = createParser(jsonFactory(), mode, input); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(JsonParser.NumberType.LONG, p.getNumberType()); + assertEquals(Long.MAX_VALUE, p.getLongValue()); +- ++ + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(JsonParser.NumberType.LONG, p.getNumberType()); + assertEquals(Long.MIN_VALUE, p.getLongValue()); +@@ -297,8 +335,16 @@ public class NumberParsingTest + assertEquals(JsonParser.NumberType.LONG, p.getNumberType()); + assertEquals(belowMinInt, p.getLongValue()); + +- +- assertToken(JsonToken.END_ARRAY, p.nextToken()); ++ assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); ++ assertEquals(JsonParser.NumberType.LONG, p.getNumberType()); ++ assertEquals(belowMaxLong, p.getLongValue()); ++ ++ assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); ++ assertEquals(JsonParser.NumberType.LONG, p.getNumberType()); ++ assertEquals(aboveMinLong, p.getLongValue()); ++ ++ ++ assertToken(JsonToken.END_ARRAY, p.nextToken()); + p.close(); + } + } +@@ -345,13 +391,13 @@ public class NumberParsingTest + cbuf = aboveMaxLong.toCharArray(); + assertFalse(NumberInput.inLongRange(cbuf, 0, cbuf.length, false)); + } +- ++ + /* + /********************************************************************** + /* Tests, BigXxx + /********************************************************************** + */ +- ++ + public void testBigDecimalRange() throws Exception + { + for (int mode : ALL_MODES) { +@@ -361,7 +407,7 @@ public class NumberParsingTest + BigInteger big = new BigDecimal(Long.MAX_VALUE).toBigInteger(); + big = big.add(BigInteger.ONE); + String input = "[ "+small+" , "+big+"]"; +- JsonParser p = createParser(mode, input); ++ JsonParser p = createParser(jsonFactory(), mode, input); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(JsonParser.NumberType.BIG_INTEGER, p.getNumberType()); +@@ -369,7 +415,7 @@ public class NumberParsingTest + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(JsonParser.NumberType.BIG_INTEGER, p.getNumberType()); + assertEquals(big, p.getBigIntegerValue()); +- assertToken(JsonToken.END_ARRAY, p.nextToken()); ++ assertToken(JsonToken.END_ARRAY, p.nextToken()); + p.close(); + } + } +@@ -385,7 +431,7 @@ public class NumberParsingTest + BigInteger biggie = new BigInteger(NUMBER_STR); + + for (int mode : ALL_MODES) { +- JsonParser p = createParser(mode, NUMBER_STR +" "); ++ JsonParser p = createParser(jsonFactory(), mode, NUMBER_STR +" "); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(JsonParser.NumberType.BIG_INTEGER, p.getNumberType()); + assertEquals(NUMBER_STR, p.getText()); +@@ -396,14 +442,62 @@ public class NumberParsingTest + + /* + /********************************************************************** +- /* Tests, floating point (basic) ++ /* Tests, int/long/BigInteger via E-notation (engineering) + /********************************************************************** + */ + ++ public void testBigIntegerWithENotation() throws Exception { ++ final String DOC = "1e5 "; ++ for (int mode : ALL_MODES) { ++ try (JsonParser p = createParser(jsonFactory(), mode, DOC)) { ++ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); ++ assertEquals(100000L, p.getBigIntegerValue().longValue()); ++ } ++ } ++ } ++ ++ public void testLongWithENotation() throws Exception { ++ final String DOC = "1e5 "; ++ for (int mode : ALL_MODES) { ++ try (JsonParser p = createParser(jsonFactory(), mode, DOC)) { ++ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); ++ assertEquals(100000L, p.getLongValue()); ++ } ++ } ++ } ++ ++ public void testIntWithENotation() throws Exception { ++ final String DOC = "1e5 "; ++ for (int mode : ALL_MODES) { ++ try (JsonParser p = createParser(jsonFactory(), mode, DOC)) { ++ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); ++ assertEquals(100000, p.getIntValue()); ++ } ++ } ++ } ++ ++ public void testLargeBigIntegerWithENotation() throws Exception { ++ // slightly bigger than Double.MAX_VALUE (we need to avoid parsing as double in this case) ++ final String DOC = "2e308 "; ++ final BigInteger expected = new BigDecimal(DOC.trim()).toBigInteger(); ++ for (int mode : ALL_MODES) { ++ try (JsonParser p = createParser(jsonFactory(), mode, DOC)) { ++ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); ++ assertEquals(expected, p.getBigIntegerValue()); ++ } ++ } ++ } ++ ++ /* ++ /********************************************************************** ++ /* Tests, floating point (basic) ++ /********************************************************************** ++ */ ++ + public void testSimpleDouble() throws Exception + { + final String[] INPUTS = new String[] { +- "1234.00", "2.1101567E-16", "1.0e5", "0.0", "1.0", "-1.0", ++ "1234.00", "2.1101567E-16", "1.0e5", "0.0", "1.0", "-1.0", + "-0.5", "-12.9", "-999.0", + "2.5e+5", "9e4", "-12e-3", "0.25", + }; +@@ -411,12 +505,12 @@ public class NumberParsingTest + for (int i = 0; i < INPUTS.length; ++i) { + + // First in array +- ++ + String STR = INPUTS[i]; + double EXP_D = Double.parseDouble(STR); + String DOC = "["+STR+"]"; + +- JsonParser p = createParser(mode, DOC+" "); ++ JsonParser p = createParser(jsonFactory(), mode, DOC+" "); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + + assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); +@@ -429,7 +523,7 @@ public class NumberParsingTest + p.close(); + + // then outside +- p = createParser(mode, STR + " "); ++ p = createParser(jsonFactory(), mode, STR + " "); + JsonToken t = null; + + try { +@@ -437,7 +531,7 @@ public class NumberParsingTest + } catch (Exception e) { + throw new Exception("Failed to parse input '"+STR+"' (parser of type "+p.getClass().getSimpleName()+")", e); + } +- ++ + assertToken(JsonToken.VALUE_NUMBER_FLOAT, t); + assertEquals(STR, p.getText()); + if (mode != MODE_DATA_INPUT) { +@@ -482,73 +576,6 @@ public class NumberParsingTest + } + } + +- /* +- /********************************************************************** +- /* Tests, BigDecimal (core#677) +- /********************************************************************** +- */ +- +- public void testBigBigDecimalsBytes() throws Exception +- { +- _testBigBigDecimals(MODE_INPUT_STREAM); +- _testBigBigDecimals(MODE_INPUT_STREAM_THROTTLED); +- } +- +- public void testBigBigDecimalsChars() throws Exception +- { +- _testBigBigDecimals(MODE_READER); +- } +- +- public void testBigBigDecimalsDataInput() throws Exception +- { +- _testBigBigDecimals(MODE_DATA_INPUT); +- } +- +- private void _testBigBigDecimals(int mode) throws Exception +- { +- final String BASE_FRACTION = +- "01610253934481930774151441507943554511027782188707463024288149352877602369090537" +-+"80583522838238149455840874862907649203136651528841378405339370751798532555965157588" +-+"51877960056849468879933122908090021571162427934915567330612627267701300492535817858" +-+"36107216979078343419634586362681098115326893982589327952357032253344676618872460059" +-+"52652865429180458503533715200184512956356092484787210672008123556320998027133021328" +-+"04777044107393832707173313768807959788098545050700242134577863569636367439867566923" +-+"33479277494056927358573496400831024501058434838492057410330673302052539013639792877" +-+"76670882022964335417061758860066263335250076803973514053909274208258510365484745192" +-+"39425298649420795296781692303253055152441850691276044546565109657012938963181532017" +-+"97420631515930595954388119123373317973532146157980827838377034575940814574561703270" +-+"54949003909864767732479812702835339599792873405133989441135669998398892907338968744" +-+"39682249327621463735375868408190435590094166575473967368412983975580104741004390308" +-+"45302302121462601506802738854576700366634229106405188353120298347642313881766673834" +-+"60332729485083952142460470270121052469394888775064758246516888122459628160867190501" +-+"92476878886543996441778751825677213412487177484703116405390741627076678284295993334" +-+"23142914551517616580884277651528729927553693274406612634848943914370188078452131231" +-+"17351787166509190240927234853143290940647041705485514683182501795615082930770566118" +-+"77488417962195965319219352314664764649802231780262169742484818333055713291103286608" +-+"64318433253572997833038335632174050981747563310524775762280529871176578487487324067" +-+"90242862159403953039896125568657481354509805409457993946220531587293505986329150608" +-+"18702520420240989908678141379300904169936776618861221839938283876222332124814830207" +-+"073816864076428273177778788053613345444299361357958409716099682468768353446625063"; +- +- for (String asText : new String[] { +- "50."+BASE_FRACTION, +- "-37."+BASE_FRACTION, +- "0.00"+BASE_FRACTION, +- "-0.012"+BASE_FRACTION, +- "9999998."+BASE_FRACTION, +- "-8888392."+BASE_FRACTION, +- }) { +- final String DOC = "[ "+asText+" ]"; +- +- JsonParser p = createParser(mode, DOC); +- assertToken(JsonToken.START_ARRAY, p.nextToken()); +- assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); +- final BigDecimal exp = new BigDecimal(asText); +- assertEquals(exp, p.getDecimalValue()); +- p.close(); +- } +- } +- + /* + /********************************************************************** + /* Tests, misc other +@@ -562,21 +589,21 @@ public class NumberParsingTest + _testNumbers(MODE_READER); + _testNumbers(MODE_DATA_INPUT); + } +- ++ + private void _testNumbers(int mode) throws Exception + { + final String DOC = "[ -13, 8100200300, 13.5, 0.00010, -2.033 ]"; + +- JsonParser p = createParser(mode, DOC); ++ JsonParser p = createParser(jsonFactory(), mode, DOC); + + assertToken(JsonToken.START_ARRAY, p.nextToken()); +- ++ + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(-13, p.getIntValue()); + assertEquals(-13L, p.getLongValue()); + assertEquals(-13., p.getDoubleValue()); + assertEquals("-13", p.getText()); +- ++ + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(8100200300L, p.getLongValue()); + // Should get exception for overflow: +@@ -588,7 +615,7 @@ public class NumberParsingTest + } + assertEquals(8100200300.0, p.getDoubleValue()); + assertEquals("8100200300", p.getText()); +- ++ + assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); + assertEquals(13, p.getIntValue()); + assertEquals(13L, p.getLongValue()); +@@ -600,7 +627,7 @@ public class NumberParsingTest + assertEquals(0L, p.getLongValue()); + assertEquals(0.00010, p.getDoubleValue()); + assertEquals("0.00010", p.getText()); +- ++ + assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); + assertEquals(-2, p.getIntValue()); + assertEquals(-2L, p.getLongValue()); +@@ -612,7 +639,7 @@ public class NumberParsingTest + p.close(); + } + +- ++ + /** + * Method that tries to test that number parsing works in cases where + * input is split between buffer boundaries. +@@ -655,7 +682,7 @@ public class NumberParsingTest + JsonParser p; + + if (input == 0) { +- p = createParserUsingStream(DOC, "UTF-8"); ++ p = createParserUsingStream(jsonFactory(), DOC, "UTF-8"); + } else { + p = jsonFactory().createParser(DOC); + } +@@ -673,10 +700,11 @@ public class NumberParsingTest + } + + // [jackson-core#157] ++ // 19-Dec-2022, tatu: Reduce length so as not to hit too-long-number limit + public void testLongNumbers() throws Exception + { +- StringBuilder sb = new StringBuilder(9000); +- for (int i = 0; i < 9000; ++i) { ++ StringBuilder sb = new StringBuilder(900); ++ for (int i = 0; i < 900; ++i) { + sb.append('9'); + } + String NUM = sb.toString(); +@@ -685,7 +713,7 @@ public class NumberParsingTest + _testLongNumbers(f, NUM, false); + _testLongNumbers(f, NUM, true); + } +- ++ + private void _testLongNumbers(JsonFactory f, String num, boolean useStream) throws Exception + { + final String doc = "[ "+num+" ]"; +@@ -698,32 +726,6 @@ public class NumberParsingTest + assertToken(JsonToken.END_ARRAY, p.nextToken()); + } + +- // and alternate take on for #157 (with negative num) +- public void testLongNumbers2() throws Exception +- { +- StringBuilder input = new StringBuilder(); +- // test this with negative +- input.append('-'); +- for (int i = 0; i < 2100; i++) { +- input.append(1); +- } +- final String DOC = input.toString(); +- JsonFactory f = new JsonFactory(); +- _testIssue160LongNumbers(f, DOC, false); +- _testIssue160LongNumbers(f, DOC, true); +- } +- +- private void _testIssue160LongNumbers(JsonFactory f, String doc, boolean useStream) throws Exception +- { +- JsonParser p = useStream +- ? jsonFactory().createParser(doc.getBytes("UTF-8")) +- : jsonFactory().createParser(doc); +- assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); +- BigInteger v = p.getBigIntegerValue(); +- assertNull(p.nextToken()); +- assertEquals(doc, v.toString()); +- } +- + // for [jackson-core#181] + /** + * Method that tries to test that number parsing works in cases where +@@ -794,7 +796,7 @@ public class NumberParsingTest + + private void _testInvalidBooleanAccess(int mode) throws Exception + { +- JsonParser p = createParser(mode, "[ \"abc\" ]"); ++ JsonParser p = createParser(jsonFactory(), mode, "[ \"abc\" ]"); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + try { +@@ -812,10 +814,10 @@ public class NumberParsingTest + _testInvalidIntAccess(MODE_READER); + _testInvalidIntAccess(MODE_DATA_INPUT); + } +- ++ + private void _testInvalidIntAccess(int mode) throws Exception + { +- JsonParser p = createParser(mode, "[ \"abc\" ]"); ++ JsonParser p = createParser(jsonFactory(), mode, "[ \"abc\" ]"); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + try { +@@ -833,10 +835,10 @@ public class NumberParsingTest + _testInvalidLongAccess(MODE_READER); + _testInvalidLongAccess(MODE_DATA_INPUT); + } +- ++ + private void _testInvalidLongAccess(int mode) throws Exception + { +- JsonParser p = createParser(mode, "[ false ]"); ++ JsonParser p = createParser(jsonFactory(), mode, "[ false ]"); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_FALSE, p.nextToken()); + try { +@@ -864,7 +866,7 @@ public class NumberParsingTest + p = jsonFactory().createParser(new StringReader(DOC)); + _testLongerFloat(p, DOC); + p.close(); +- ++ + p = jsonFactory().createParser(new ByteArrayInputStream(DOC.getBytes("UTF-8"))); + _testLongerFloat(p, DOC); + p.close(); +@@ -879,7 +881,7 @@ public class NumberParsingTest + + public void testInvalidNumber() throws Exception { + for (int mode : ALL_MODES) { +- JsonParser p = createParser(mode, " -foo "); ++ JsonParser p = createParser(jsonFactory(), mode, " -foo "); + try { + p.nextToken(); + fail("Should not pass"); +@@ -890,6 +892,15 @@ public class NumberParsingTest + } + } + ++ public void testNegativeMaxNumberLength() { ++ try { ++ StreamReadConstraints src = StreamReadConstraints.builder().maxNumberLength(-1).build(); ++ fail("expected IllegalArgumentException; instead built: "+src); ++ } catch (IllegalArgumentException iae) { ++ verifyException(iae, "Cannot set maxNumberLength to a negative value"); ++ } ++ } ++ + /* + /********************************************************** + /* Helper methods +diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java b/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java +index 7453ae9..08ae00c 100644 +--- a/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java ++++ b/src/test/java/com/fasterxml/jackson/core/util/TestDelegates.java +@@ -101,7 +101,7 @@ public class TestDelegates extends com.fasterxml.jackson.core.BaseTest + @Override + public T treeToValue(TreeNode n, Class valueType) { + return null; +- } ++ } + } + + static class BogusTree implements TreeNode { +@@ -198,9 +198,16 @@ public class TestDelegates extends com.fasterxml.jackson.core.BaseTest + */ + public void testParserDelegate() throws IOException + { ++ final int MAX_NUMBER_LEN = 200; + final String TOKEN ="foo"; + +- JsonParser parser = JSON_F.createParser("[ 1, true, null, { \"a\": \"foo\" }, \"AQI=\" ]"); ++ StreamReadConstraints CUSTOM_CONSTRAINTS = StreamReadConstraints.builder() ++ .maxNumberLength(MAX_NUMBER_LEN) ++ .build(); ++ JsonFactory jsonF = JsonFactory.builder() ++ .streamReadConstraints(CUSTOM_CONSTRAINTS) ++ .build(); ++ JsonParser parser = jsonF.createParser("[ 1, true, null, { \"a\": \"foo\" }, \"AQI=\" ]"); + JsonParserDelegate del = new JsonParserDelegate(parser); + + // Basic capabilities for parser: +@@ -209,6 +216,8 @@ public class TestDelegates extends com.fasterxml.jackson.core.BaseTest + assertFalse(del.canReadTypeId()); + assertFalse(del.requiresCustomCodec()); + assertEquals(parser.version(), del.version()); ++ assertSame(parser.streamReadConstraints(), del.streamReadConstraints()); ++ assertSame(parser.getReadCapabilities(), del.getReadCapabilities()); + + // configuration + assertFalse(del.isEnabled(JsonParser.Feature.ALLOW_COMMENTS)); +@@ -256,7 +265,7 @@ public class TestDelegates extends com.fasterxml.jackson.core.BaseTest + assertEquals(NumberType.INT, del.getNumberType()); + assertEquals(Integer.valueOf(1), del.getNumberValue()); + assertNull(del.getEmbeddedObject()); +- ++ + assertToken(JsonToken.VALUE_TRUE, del.nextToken()); + assertTrue(del.getBooleanValue()); + assertEquals(parser.getCurrentLocation(), del.getCurrentLocation()); +@@ -276,13 +285,13 @@ public class TestDelegates extends com.fasterxml.jackson.core.BaseTest + assertToken(JsonToken.VALUE_STRING, del.nextToken()); + assertTrue(del.hasTextCharacters()); + assertEquals("foo", del.getText()); +- ++ + assertToken(JsonToken.END_OBJECT, del.nextToken()); + assertEquals(TOKEN, del.getCurrentValue()); + + assertToken(JsonToken.VALUE_STRING, del.nextToken()); + assertArrayEquals(new byte[] { 1, 2 }, del.getBinaryValue()); +- ++ + assertToken(JsonToken.END_ARRAY, del.nextToken()); + + del.close(); +@@ -323,7 +332,7 @@ public class TestDelegates extends com.fasterxml.jackson.core.BaseTest + // initial state + assertNull(del.getSchema()); + assertNull(del.getPrettyPrinter()); +- ++ + del.writeStartArray(); + + assertEquals(1, del.getOutputBuffered()); +@@ -349,11 +358,11 @@ public class TestDelegates extends com.fasterxml.jackson.core.BaseTest + del.writeEndArray(); + + del.writeEndArray(); +- ++ + del.flush(); + del.close(); +- assertTrue(del.isClosed()); +- assertTrue(g0.isClosed()); ++ assertTrue(del.isClosed()); ++ assertTrue(g0.isClosed()); + assertEquals("[13,1,0.5,137,null,false,\"foo\",{},[]]", sw.toString()); + + g0.close(); +@@ -379,7 +388,7 @@ public class TestDelegates extends com.fasterxml.jackson.core.BaseTest + + g0.close(); + } +- ++ + public void testGeneratorDelegateComments() throws IOException + { + StringWriter sw = new StringWriter(); +@@ -420,13 +429,13 @@ public class TestDelegates extends com.fasterxml.jackson.core.BaseTest + assertToken(JsonToken.VALUE_FALSE, p.nextToken()); + del.copyCurrentEvent(p); + g0.writeEndArray(); +- ++ + del.close(); + g0.close(); + p.close(); + assertEquals("[123,false]", sw.toString()); + } +- ++ + public void testNotDelegateCopyMethods() throws IOException + { + JsonParser jp = JSON_F.createParser("[{\"a\":[1,2,{\"b\":3}],\"c\":\"d\"},{\"e\":false},null]"); +diff --git a/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java b/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java +index f7eb410..c031213 100644 +--- a/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java ++++ b/src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java +@@ -1,7 +1,5 @@ + package com.fasterxml.jackson.core.util; + +-import com.fasterxml.jackson.core.io.BigDecimalParser; +- + public class TestTextBuffer + extends com.fasterxml.jackson.core.BaseTest + { +@@ -9,7 +7,7 @@ public class TestTextBuffer + * Trivially simple basic test to ensure all basic append + * methods work + */ +- public void testSimple() ++ public void testSimple() throws Exception + { + TextBuffer tb = new TextBuffer(new BufferRecycler()); + tb.append('a'); +@@ -24,7 +22,7 @@ public class TestTextBuffer + assertNotNull(tb.expandCurrentSegment()); + } + +- public void testLonger() ++ public void testLonger() throws Exception + { + TextBuffer tb = new TextBuffer(null); + for (int i = 0; i < 2000; ++i) { +@@ -39,7 +37,7 @@ public class TestTextBuffer + assertTrue(tb.hasTextAsCharacters()); + } + +- public void testLongAppend() ++ public void testLongAppend() throws Exception + { + final int len = TextBuffer.MAX_SEGMENT_LEN * 3 / 2; + StringBuilder sb = new StringBuilder(len); +@@ -48,7 +46,7 @@ public class TestTextBuffer + } + final String STR = sb.toString(); + final String EXP = "a" + STR + "c"; +- ++ + // ok: first test with String: + TextBuffer tb = new TextBuffer(new BufferRecycler()); + tb.append('a'); +@@ -56,7 +54,7 @@ public class TestTextBuffer + tb.append('c'); + assertEquals(len+2, tb.size()); + assertEquals(EXP, tb.contentsAsString()); +- ++ + // then char[] + tb = new TextBuffer(new BufferRecycler()); + tb.append('a'); +@@ -67,7 +65,7 @@ public class TestTextBuffer + } + + // [core#152] +- public void testExpand() ++ public void testExpand() throws Exception + { + TextBuffer tb = new TextBuffer(new BufferRecycler()); + char[] buf = tb.getCurrentSegment(); +@@ -84,7 +82,7 @@ public class TestTextBuffer + } + + // [core#182] +- public void testEmpty() { ++ public void testEmpty() throws Exception { + TextBuffer tb = new TextBuffer(new BufferRecycler()); + tb.resetWithEmpty(); + +@@ -93,13 +91,13 @@ public class TestTextBuffer + assertTrue(tb.getTextBuffer().length == 0); + } + +- public void testResetWithAndSetCurrentAndReturn() { ++ public void testResetWithAndSetCurrentAndReturn() throws Exception { + TextBuffer textBuffer = new TextBuffer(null); + textBuffer.resetWith('l'); + textBuffer.setCurrentAndReturn(349); + } + +- public void testGetCurrentSegment() { ++ public void testGetCurrentSegment() throws Exception { + TextBuffer textBuffer = new TextBuffer(null); + textBuffer.emptyAndGetCurrentSegment(); + // 26-Aug-2019, tatu: Value depends on "minimum segment size": +@@ -109,7 +107,7 @@ public class TestTextBuffer + assertEquals(500, textBuffer.size()); + } + +- public void testAppendTakingTwoAndThreeInts() { ++ public void testAppendTakingTwoAndThreeInts() throws Exception { + BufferRecycler bufferRecycler = new BufferRecycler(); + TextBuffer textBuffer = new TextBuffer(bufferRecycler); + textBuffer.ensureNotShared(); +@@ -120,7 +118,7 @@ public class TestTextBuffer + assertEquals(3, textBuffer.getCurrentSegmentSize()); + } + +- public void testEnsureNotSharedAndResetWithString() { ++ public void testEnsureNotSharedAndResetWithString() throws Exception { + BufferRecycler bufferRecycler = new BufferRecycler(); + TextBuffer textBuffer = new TextBuffer(bufferRecycler); + textBuffer.resetWithString(""); +@@ -132,18 +130,7 @@ public class TestTextBuffer + assertEquals(0, textBuffer.getCurrentSegmentSize()); + } + +- public void testContentsAsDecimalThrowsNumberFormatException() { +- TextBuffer textBuffer = new TextBuffer( null); +- +- try { +- textBuffer.contentsAsDecimal(); +- fail("Expecting exception: NumberFormatException"); +- } catch(NumberFormatException e) { +- assertEquals(BigDecimalParser.class.getName(), e.getStackTrace()[0].getClassName()); +- } +- } +- +- public void testGetTextBufferAndEmptyAndGetCurrentSegmentAndFinishCurrentSegment() { ++ public void testGetTextBufferAndEmptyAndGetCurrentSegmentAndFinishCurrentSegment() throws Exception { + BufferRecycler bufferRecycler = new BufferRecycler(); + TextBuffer textBuffer = new TextBuffer(bufferRecycler); + textBuffer.emptyAndGetCurrentSegment(); +@@ -153,7 +140,7 @@ public class TestTextBuffer + assertEquals(200, textBuffer.size()); + } + +- public void testGetTextBufferAndAppendTakingCharAndContentsAsArray() { ++ public void testGetTextBufferAndAppendTakingCharAndContentsAsArray() throws Exception { + BufferRecycler bufferRecycler = new BufferRecycler(); + TextBuffer textBuffer = new TextBuffer(bufferRecycler); + textBuffer.append('('); +@@ -163,7 +150,7 @@ public class TestTextBuffer + assertEquals(1, textBuffer.getCurrentSegmentSize()); + } + +- public void testGetTextBufferAndResetWithString() { ++ public void testGetTextBufferAndResetWithString() throws Exception { + BufferRecycler bufferRecycler = new BufferRecycler(); + TextBuffer textBuffer = new TextBuffer(bufferRecycler); + textBuffer.resetWithString(""); +@@ -175,7 +162,7 @@ public class TestTextBuffer + assertTrue(textBuffer.hasTextAsCharacters()); + } + +- public void testResetWithString() { ++ public void testResetWithString() throws Exception { + BufferRecycler bufferRecycler = new BufferRecycler(); + TextBuffer textBuffer = new TextBuffer(bufferRecycler); + textBuffer.ensureNotShared(); +@@ -196,7 +183,7 @@ public class TestTextBuffer + assertEquals(1, textBuffer.getCurrentSegmentSize()); + } + +- public void testGetSizeFinishCurrentSegmentAndResetWith() { ++ public void testGetSizeFinishCurrentSegmentAndResetWith() throws Exception { + TextBuffer textBuffer = new TextBuffer(null); + textBuffer.resetWith('.'); + textBuffer.finishCurrentSegment(); diff -Nru jackson-core-2.14.1/debian/patches/series jackson-core-2.14.1/debian/patches/series --- jackson-core-2.14.1/debian/patches/series 2022-12-19 18:42:08.000000000 +0000 +++ jackson-core-2.14.1/debian/patches/series 2026-06-07 17:18:04.000000000 +0000 @@ -1,2 +1,3 @@ 02-skip-jacoco.patch 01-no-bundle.patch +CVE-2025-52999.patch