This is an automated email from the ASF dual-hosted git repository. snuyanzin pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/flink.git
commit 96a45c1234dd4592852b9a93f86b4a36b40bf23a Author: Ramin Gharib <[email protected]> AuthorDate: Wed Jul 22 18:00:43 2026 +0200 [FLINK-40217][core] Reject non-finite numbers in `VARIANT` JSON conversion PARSE_JSON accepted JSON numbers outside the double range, such as 1e400, and silently stored them as +/-Infinity. Variant.toJson() then emitted bare Infinity/-Infinity tokens, which are invalid JSON and cannot be parsed back by PARSE_JSON, so the round trip was broken. parseFloatingPoint now rejects a non-finite result from getDoubleValue() with a clear parse error. PARSE_JSON surfaces the failure and TRY_PARSE_JSON returns NULL, so a parsed Variant can never hold a non-finite value. As a defensive safeguard for the builder API, which can still inject non-finite values, toJson() now throws for non-finite DOUBLE and FLOAT values instead of emitting invalid tokens. --- .../apache/flink/types/variant/BinaryVariant.java | 25 ++++++++++++++++++---- .../variant/BinaryVariantInternalBuilder.java | 15 +++++++++++-- .../variant/BinaryVariantInternalBuilderTest.java | 11 ++++++++++ .../flink/types/variant/BinaryVariantTest.java | 18 ++++++++++++++++ 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/flink-core/src/main/java/org/apache/flink/types/variant/BinaryVariant.java b/flink-core/src/main/java/org/apache/flink/types/variant/BinaryVariant.java index ce5664d34e3..c5dd0bf3efe 100644 --- a/flink-core/src/main/java/org/apache/flink/types/variant/BinaryVariant.java +++ b/flink-core/src/main/java/org/apache/flink/types/variant/BinaryVariant.java @@ -356,8 +356,16 @@ public final class BinaryVariant implements Variant { sb.append(escapeJson(BinaryVariantUtil.getString(value, pos))); break; case DOUBLE: - sb.append(BinaryVariantUtil.getDouble(value, pos)); - break; + { + final double d = BinaryVariantUtil.getDouble(value, pos); + if (Double.isInfinite(d) || Double.isNaN(d)) { + throw new VariantTypeException( + String.format( + "Non-finite value %s cannot be serialized to JSON.", d)); + } + sb.append(d); + break; + } case DECIMAL: sb.append(BinaryVariantUtil.getDecimal(value, pos).toPlainString()); break; @@ -382,8 +390,17 @@ public final class BinaryVariant implements Variant { .atZone(ZoneOffset.UTC))); break; case FLOAT: - sb.append(BinaryVariantUtil.getFloat(value, pos)); - break; + { + final float f = BinaryVariantUtil.getFloat(value, pos); + if (Float.isInfinite(f) || Float.isNaN(f)) { + throw new VariantTypeException( + String.format( + "Non-finite value %s cannot be serialized to JSON.", + (double) f)); + } + sb.append(f); + break; + } case BYTES: appendQuoted( sb, diff --git a/flink-core/src/main/java/org/apache/flink/types/variant/BinaryVariantInternalBuilder.java b/flink-core/src/main/java/org/apache/flink/types/variant/BinaryVariantInternalBuilder.java index 928c267f365..1674d166266 100644 --- a/flink-core/src/main/java/org/apache/flink/types/variant/BinaryVariantInternalBuilder.java +++ b/flink-core/src/main/java/org/apache/flink/types/variant/BinaryVariantInternalBuilder.java @@ -91,6 +91,7 @@ public class BinaryVariantInternalBuilder { new VariantTypeException("VARIANT_SIZE_LIMIT"); public static final VariantTypeException VARIANT_DUPLICATE_KEY_EXCEPTION = new VariantTypeException("VARIANT_DUPLICATE_KEY"); + private static final JsonFactory JSON_FACTORY = new JsonFactory(); public BinaryVariantInternalBuilder(boolean allowDuplicateKeys) { this.allowDuplicateKeys = allowDuplicateKeys; @@ -103,7 +104,7 @@ public class BinaryVariantInternalBuilder { */ public static BinaryVariant parseJson(String json, boolean allowDuplicateKeys) throws IOException { - try (JsonParser parser = new JsonFactory().createParser(json)) { + try (JsonParser parser = JSON_FACTORY.createParser(json)) { parser.nextToken(); return parseJson(parser, allowDuplicateKeys); } @@ -622,7 +623,17 @@ public class BinaryVariantInternalBuilder { private void parseFloatingPoint(JsonParser parser) throws IOException { if (!tryParseDecimal(parser.getText())) { - appendDouble(parser.getDoubleValue()); + final double d = parser.getDoubleValue(); + // Jackson coerces out-of-range numbers like 1e400 to +/-Infinity. Reject them instead + // of storing a non-finite double that toJson() could not render as valid JSON. + if (Double.isInfinite(d) || Double.isNaN(d)) { + throw new JsonParseException( + parser, + String.format( + "Numeric value '%s' is out of the range of double precision and cannot be stored as a Variant.", + parser.getText())); + } + appendDouble(d); } } diff --git a/flink-core/src/test/java/org/apache/flink/types/variant/BinaryVariantInternalBuilderTest.java b/flink-core/src/test/java/org/apache/flink/types/variant/BinaryVariantInternalBuilderTest.java index 924e538f40a..cec12149ceb 100644 --- a/flink-core/src/test/java/org/apache/flink/types/variant/BinaryVariantInternalBuilderTest.java +++ b/flink-core/src/test/java/org/apache/flink/types/variant/BinaryVariantInternalBuilderTest.java @@ -19,6 +19,8 @@ package org.apache.flink.types.variant; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.io.IOException; import java.math.BigDecimal; @@ -120,6 +122,15 @@ class BinaryVariantInternalBuilderTest { assertThat(variant.getField("k2").getDecimal()).isEqualTo(BigDecimal.valueOf(1.5)); } + @ParameterizedTest + @ValueSource(strings = {"NaN", "Infinity", "-Infinity", "1e400", "-1e400"}) + void testParseJsonRejectsNonFiniteNumbers(final String nonFiniteNumber) { + // NaN and the infinities are not valid JSON; 1e400 is valid JSON but overflows the double + // range. Both must be rejected so PARSE_JSON errors and TRY_PARSE_JSON returns NULL. + assertThatThrownBy(() -> BinaryVariantInternalBuilder.parseJson(nonFiniteNumber, false)) + .isInstanceOf(IOException.class); + } + @Test void testAppendFloat() { BinaryVariantInternalBuilder builder = new BinaryVariantInternalBuilder(false); diff --git a/flink-core/src/test/java/org/apache/flink/types/variant/BinaryVariantTest.java b/flink-core/src/test/java/org/apache/flink/types/variant/BinaryVariantTest.java index 83896ec53e1..77235e968ee 100644 --- a/flink-core/src/test/java/org/apache/flink/types/variant/BinaryVariantTest.java +++ b/flink-core/src/test/java/org/apache/flink/types/variant/BinaryVariantTest.java @@ -20,6 +20,8 @@ package org.apache.flink.types.variant; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.math.BigDecimal; import java.time.Instant; @@ -236,6 +238,22 @@ class BinaryVariantTest { .isEqualTo("{" + "\"list\":[\"hello\",1]," + "\"object\":{\"ff\":10.0,\"ss\":1}}"); } + @ParameterizedTest + @ValueSource(doubles = {Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN}) + void testToJsonRejectsNonFiniteDouble(final double nonFinite) { + assertThatThrownBy(() -> builder.of(nonFinite).toJson()) + .isInstanceOf(VariantTypeException.class) + .hasMessageContaining("cannot be serialized to JSON"); + } + + @ParameterizedTest + @ValueSource(floats = {Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN}) + void testToJsonRejectsNonFiniteFloat(final float nonFinite) { + assertThatThrownBy(() -> builder.of(nonFinite).toJson()) + .isInstanceOf(VariantTypeException.class) + .hasMessageContaining("cannot be serialized to JSON"); + } + @Test void testVariantException() { assertThatThrownBy(() -> new BinaryVariant(new byte[0], new byte[0]))
