[ 
https://issues.apache.org/jira/browse/FLINK-40217?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Sergey Nuyanzin resolved FLINK-40217.
-------------------------------------
    Fix Version/s: 2.4.0
       Resolution: Fixed

> Variant.toJson() can emit invalid JSON for numbers that overflow to 
> non-finite doubles
> --------------------------------------------------------------------------------------
>
>                 Key: FLINK-40217
>                 URL: https://issues.apache.org/jira/browse/FLINK-40217
>             Project: Flink
>          Issue Type: Bug
>          Components: API / Type Serialization System, Table SQL / API
>    Affects Versions: 2.3.0, 2.2.1, 2.1.3
>            Reporter: Ramin Gharib
>            Assignee: Ramin Gharib
>            Priority: Minor
>              Labels: pull-request-available
>             Fix For: 2.4.0
>
>
>  
> {{PARSE_JSON}} accepts a JSON number whose magnitude exceeds the {{double}} 
> range and silently stores it as a non-finite {{{}double{}}}. 
> {{Variant.toJson()}} then serializes that value as the bare token 
> {{Infinity}} / {{{}-Infinity{}}}, which is not valid JSON. The result cannot 
> be read back by {{{}PARSE_JSON{}}}, so the round-trip 
> {{PARSE_JSON(v).toJson()}} is not closed over valid JSON.
> *Reproduction (SQL)*
> {code:java}
> -- Accepted. Overflows to a non-finite double:
> SELECT PARSE_JSON('1e400');
> -- +----------+
> -- |   EXPR$0 |
> -- +----------+
> -- | Infinity |   <-- invalid JSON emitted by toJson()
> -- +----------+
> -- '-1e400' likewise yields -Infinity.
> -- The reverse direction is already (correctly) rejected:
> SELECT PARSE_JSON('Infinity');
> -- Caused by: com.fasterxml.jackson.core.JsonParseException:
> --   Non-standard token 'Infinity': enable 
> `JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS` to allow 
> {code}
> So {{PARSE_JSON}} produces output ({{{}Infinity{}}}) that {{PARSE_JSON}} 
> itself refuses to accept.
> *Root cause*
> Two spots in {{{}flink-core{}}}, package 
> {{{}org.apache.flink.types.variant{}}}:
> {{1. BinaryVariantInternalBuilder.parseFloatingPoint}} — {{1e400}} uses 
> scientific notation, so {{tryParseDecimal}} returns false and the value goes 
> through {{{}appendDouble(parser.getDoubleValue()){}}}. Jackson's 
> {{getDoubleValue()}} coerces the out-of-range number to 
> {{{}Double.POSITIVE_INFINITY{}}}. The overflow is silent.
> {code:java}
> private void parseFloatingPoint(JsonParser parser) throws IOException {
>     if (!tryParseDecimal(parser.getText())) {
>         appendDouble(parser.getDoubleValue()); // 1e400 -> +Infinity, no error
>     }
> } {code}
> {{2. BinaryVariant.toJsonImpl}} — the {{DOUBLE}} and {{FLOAT}} cases append 
> via {{{}StringBuilder.append(double/float){}}}, i.e. 
> {{{}Double.toString(...){}}}, which yields {{Infinity}} / {{-Infinity}} / 
> {{{}NaN{}}}. None are valid JSON.
> {code:java}
> case DOUBLE:
>     sb.append(BinaryVariantUtil.getDouble(value, pos)); // "Infinity" etc.
>     break;
> ...
> case FLOAT:
>     sb.append(BinaryVariantUtil.getFloat(value, pos));
>     break; {code}
> The builder API ({{{}Variant.newBuilder().of(Double.NaN){}}}) can also place 
> a non-finite value into a Variant, which hits the same {{toJson()}} defect. 
> {{PARSE_JSON}} can only reach {{±Infinity}} (via overflow); {{NaN}} is only 
> reachable through the builder.
> *Expected behavior*
> {{toJson()}} must always produce valid JSON, and it should be readable back 
> by {{{}PARSE_JSON{}}}. A Variant sourced from JSON should never hold a value 
> that JSON cannot represent.
> *Proposed fix*
>  * *Preferred (parse side):* reject the lossy coercion in 
> {{{}parseFloatingPoint{}}}. If {{getDoubleValue()}} is non-finite while the 
> source token was finite text, throw a clear parse error. This makes 
> {{PARSE_JSON('1e400')}} fail the same way {{PARSE_JSON('Infinity')}} already 
> does, and keeps non-finite values out of JSON-sourced Variants entirely.
>  * *Defense-in-depth (serialize side):* in {{{}toJsonImpl{}}}, handle 
> non-finite {{{}double{}}}/{{{}float{}}} explicitly so a Variant built via the 
> builder API cannot emit invalid JSON. Options: emit {{null}} (matches 
> {{JSON.stringify}} semantics) or throw. Emitting {{null}} is lossy; throwing 
> is safer for a serializer that advertises valid-JSON output. Maintainer's 
> call.
> Recommendation: do the parse-side rejection as the root-cause fix, plus a 
> guard in {{toJson()}} so the builder path is also safe.
> *Verifying the change*
> Add unit tests in {{BinaryVariantTest}} / the {{PARSE_JSON}} function tests: 
> assert {{PARSE_JSON('1e400')}} and {{'-1e400'}} fail with a clear message, 
> and that {{toJson()}} never returns a string containing {{Infinity}} / 
> {{{}NaN{}}}. Add a round-trip test asserting {{PARSE_JSON(v.toJson())}} 
> succeeds for any Variant produced by {{{}PARSE_JSON{}}}.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to