Skip to content

Fix JsonParser.isNaN() to be stable, only indicate explicit NaN values; not ones due to coercion #1150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions src/main/java/com/fasterxml/jackson/core/JsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import com.fasterxml.jackson.core.async.NonBlockingInputFeeder;
import com.fasterxml.jackson.core.exc.InputCoercionException;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.core.util.RequestPayload;
Expand Down Expand Up @@ -1397,16 +1398,21 @@ public int currentTokenId() {
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
* positive/negative infinity!): not supported by all formats,
* but often supported for {@link JsonToken#VALUE_NUMBER_FLOAT}.
* NOTE: roughly equivalent to calling <code>!Double.isFinite()</code>
* on value you would get from calling {@link #getDoubleValue()}.
*
* @return {@code True} if the current token is of type {@link JsonToken#VALUE_NUMBER_FLOAT}
* but represents a "Not a Number"; {@code false} for other tokens and regular
* floating-point numbers
* Access for checking whether current token is a special
* "not-a-number" (NaN) token (including both "NaN" AND
* positive/negative infinity!). These values are not supported by all formats:
* JSON, for example, only supports them if
* {@link JsonReadFeature#ALLOW_NON_NUMERIC_NUMBERS} is enabled.
*<p>
* NOTE: in case where numeric value is outside range of requested type --
* most notably {@link java.lang.Float} or {@link java.lang.Double} -- and
* decoding results effectively in a NaN value, this method DOES NOT return
* {@code true}: only explicit incoming markers do.
* This is because value could still be accessed as a valid {@link BigDecimal}.
*
* @return {@code True} if the current token is reported as {@link JsonToken#VALUE_NUMBER_FLOAT}
* and represents a "Not a Number" value; {@code false} for other tokens and regular
* floating-point numbers.
*
* @throws IOException for low-level read issues, or
* {@link JsonParseException} for decoding problems
Expand Down
27 changes: 21 additions & 6 deletions src/main/java/com/fasterxml/jackson/core/base/ParserBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,21 @@ public abstract class ParserBase extends ParserMinimalBase
*/
protected String _numberString;

/**
* Marker for explicit "Not a Number" (NaN) values that may be read
* by some formats: this includes positive and negative infinity,
* as well as "NaN" result for some arithmetic operations.
*<p>
* In case of JSON, such values can only be handled with non-standard
* processing: for some other formats they can be passed normally.
*<p>
* NOTE: this marker is NOT set in case of value overflow/underflow for
* {@code double} or {@code float} values.
*
* @since 2.17
*/
protected boolean _numberIsNaN;

// And then other information about value itself

/**
Expand Down Expand Up @@ -571,6 +586,7 @@ protected final JsonToken resetInt(boolean negative, int intLen)
// May throw StreamConstraintsException:
_streamReadConstraints.validateIntegerLength(intLen);
_numberNegative = negative;
_numberIsNaN = false;
_intLength = intLen;
_fractLength = 0;
_expLength = 0;
Expand All @@ -584,6 +600,7 @@ protected final JsonToken resetFloat(boolean negative, int intLen, int fractLen,
// May throw StreamConstraintsException:
_streamReadConstraints.validateFPLength(intLen + fractLen + expLen);
_numberNegative = negative;
_numberIsNaN = false;
_intLength = intLen;
_fractLength = fractLen;
_expLength = expLen;
Expand All @@ -597,17 +614,15 @@ protected final JsonToken resetAsNaN(String valueStr, double value)
_textBuffer.resetWithString(valueStr);
_numberDouble = value;
_numTypesValid = NR_DOUBLE;
_numberIsNaN = true;
return JsonToken.VALUE_NUMBER_FLOAT;
}

@Override
public boolean isNaN() throws IOException {
if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
if ((_numTypesValid & NR_DOUBLE) != 0) {
return !Double.isFinite(_getNumberDouble());
}
}
return false;
// 01-Dec-2023, tatu: [core#1137] Only return explicit NaN
return (_currToken == JsonToken.VALUE_NUMBER_FLOAT)
&& _numberIsNaN;
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,25 @@ public void testNegativeHexadecimal() throws Exception {
}
}

/**
* Test for checking that Overflow for Double value does not lead
* to bogus NaN information.
*/
public void testLargeDecimal() throws Exception {
final String value = "7976931348623157e309";
final String biggerThanDouble = "7976931348623157e309";
for (int mode : ALL_MODES) {
try (JsonParser p = createParser(mode, " " + value + " ")) {
try (JsonParser p = createParser(mode, " " + biggerThanDouble + " ")) {
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
assertEquals(new BigDecimal(value), p.getDecimalValue());
assertEquals(new BigDecimal(biggerThanDouble), p.getDecimalValue());
assertFalse(p.isNaN());
assertEquals(Double.POSITIVE_INFINITY, p.getValueAsDouble());
// PJF: we might want to fix the isNaN check to not be affected by us reading the value as a double
assertTrue(p.isNaN());
// 01-Dec-2023, tatu: [core#1137] NaN only from explicit value
assertFalse(p.isNaN());
}
}
}

//JSON does not allow numbers to have f or d suffixes
// JSON does not allow numbers to have f or d suffixes
public void testFloatMarker() throws Exception {
for (int mode : ALL_MODES) {
try (JsonParser p = createParser(mode, " -0.123f ")) {
Expand Down