fix: JsonTreeWriter accepts finite BigDecimal/BigInteger like JsonWriter#3035
fix: JsonTreeWriter accepts finite BigDecimal/BigInteger like JsonWriter#3035andrewstellman wants to merge 3 commits into
Conversation
3f6de17 to
347dd91
Compare
Marcono1234
left a comment
There was a problem hiding this comment.
Good find! Thanks for providing a fix for this.
Feel free to consider my review comments only as suggestions; I am not a direct project member.
| /** | ||
| * Regression test for BUG-001: toJson and toJsonTree must agree on a finite BigDecimal (STRICT). | ||
| */ | ||
| public class Bug001RegressionTest { |
There was a problem hiding this comment.
This test naming might be a bit arbitrary / not very expressive. Might be better to move the new test method to the existing JsonTreeWriterTest?
| boolean stringOk; | ||
| try { | ||
| Object u = gson.toJson(finiteHuge, Number.class); | ||
| stringOk = (u != null); |
There was a problem hiding this comment.
Not sure if that relevant, but with this null check the assertion below would also succeed if one implementation returns null and the other throws an exception. That would probably not be desired.
Maybe omit the try-catch and just have two assertThat(...).isNotNull(), indicating that both implementations should support it without throwing (that would also match the test method name which says "... accepts ...")?
There was a problem hiding this comment.
You're right, pushed tighter version.
9f4c6e7 to
3276aae
Compare
JsonTreeWriter.value(Number) rejects a finite BigDecimal value that JsonWriter.value(Number) accepts in STRICT mode, because the tree writer's finiteness check uses doubleValue() which can overflow for large finite BigDecimal/BigInteger values. This commit adds the failing regression test that demonstrates the divergence between the streaming and tree writer surfaces.
The tree writer's finiteness check used doubleValue(), which can overflow to Infinity for finite BigDecimal/BigInteger values like 1E400 — causing toJsonTree to reject inputs that toJson accepts. JsonWriter.value(Number) already special-cases the 'alwaysCreatesValidJsonNumber' classes (BigDecimal, BigInteger, AtomicInteger, AtomicLong) to skip the doubleValue-based oracle. This change mirrors that guard in JsonTreeWriter.value(Number), making the two writer surfaces agree. Existing tests (ToNumberPolicyTest, JsonTreeWriterTest, etc.) continue to pass; the new JsonTreeWriterFiniteBigDecimalTest now passes.
3276aae to
04f749d
Compare
JsonTreeWriter.value(Number) rejects a finite BigDecimal value that JsonWriter.value(Number) accepts in STRICT mode, because the tree writer's finiteness check uses doubleValue() which can overflow for large finite BigDecimal/BigInteger values. This commit adds the failing regression test that demonstrates the divergence between the streaming and tree writer surfaces.
04f749d to
2bff7f8
Compare
Summary
JsonTreeWriter.value(Number)rejects a finiteBigDecimalorBigIntegerthatJsonWriter.value(Number)accepts. The two writer surfaces should agree on what constitutes a valid JSON number, butJsonTreeWriter's finiteness check usesdoubleValue()— which overflows toInfinityfor finite values likeBigDecimal("1E400").JsonWriteralready has a whitelist (alwaysCreatesValidJsonNumber) that skips thedoubleValueoracle forBigDecimal/BigInteger/AtomicInteger/AtomicLong. This PR mirrors that guard inJsonTreeWriter.Reproduction
finiteHugeis mathematically finite, the streaming serializer accepts it, buttoJsonTreerejects it because:BigDecimal("1E400").doubleValue()returnsDouble.POSITIVE_INFINITYeven though the BigDecimal itself is finite.Root cause
doubleValue()is the wrong oracle forBigDecimal/BigIntegerfiniteness — those types can represent values outsidedouble's range.JsonWriter.value(Number)already handles this correctly via thealwaysCreatesValidJsonNumberwhitelist (JsonWriter.java:730-741), which skips theVALID_JSON_NUMBER_PATTERNvalidation for known-always-valid number classes. The tree writer never got the equivalent guard.Fix
internal/bind/JsonTreeWriter.java:218-226— skip thedoubleValue()finiteness check when the value is aBigDecimalorBigInteger. Both classes always produce valid JSON numbertoString()output, so the check is only needed forFloat/Doubleand arbitraryNumbersubclasses wheredoubleValue()is a meaningful oracle.The new condition is
if (!isLenient() && !(value instanceof BigDecimal) && !(value instanceof BigInteger)).AtomicInteger/AtomicLongaren't included because theirdoubleValue()is exact and finite by construction.Testing
New regression test
Bug001RegressionTest.treeWriterAcceptsFiniteBigDecimalLikeStreamWriteringson/src/test/java/com/google/gson/regression/:treeOk = false,stringOk = true→ assertion fails.Existing test suites pass with the change:
JsonTreeWriterTest,ToNumberPolicyTest. The fix doesn't relax the lenient/strict semantics forFloat/Doubleor arbitrary number subclasses — onlyBigDecimal/BigIntegerget the new skip.Provenance
Discovered by Quality Playbook v1.5.8, an AI-assisted code review skill. The regression test was generated and TDD-verified (red on clean HEAD → green after fix) automatically before this PR was authored. The bug is a write-surface parallel-implementation hazard — same class as #3006 (which fixed the read-side dup-key counterpart).
Related