From d214beb59c3879b5f1688c5f97f00e34bf144bac Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Tue, 11 Feb 2025 10:47:29 +0100 Subject: [PATCH] Use 256-bit limitation for mantissa length when parsing floating point numbers (#1302) --- docs/security.md | 2 +- .../filteredgentype/FilteredGenType.json | 200 +++++++++--------- .../scala/zio/json/golden/GoldenSpec.scala | 4 +- .../scala/zio/json/internal/SafeNumbers.scala | 10 +- .../zio/json/internal/UnsafeNumbers.scala | 34 ++- .../json/internal/SafeNumbersBenchmarks.scala | 4 +- .../scala/zio/json/internal/SafeNumbers.scala | 10 +- .../zio/json/internal/UnsafeNumbers.scala | 34 ++- .../scala/zio/json/data/geojson/GeoJSON.scala | 2 +- .../scala/zio/json/internal/SafeNumbers.scala | 10 +- .../zio/json/internal/UnsafeNumbers.scala | 34 ++- .../main/scala/zio/json/internal/lexer.scala | 5 +- .../src/test/scala/zio/json/CodecSpec.scala | 2 +- .../src/test/scala/zio/json/DecoderSpec.scala | 16 +- .../src/test/scala/zio/json/EncoderSpec.scala | 70 +++++- .../shared/src/test/scala/zio/json/Gens.scala | 8 +- .../zio/json/internal/SafeNumbersSpec.scala | 11 +- 17 files changed, 258 insertions(+), 198 deletions(-) diff --git a/docs/security.md b/docs/security.md index 194b5cfda..051b6b342 100644 --- a/docs/security.md +++ b/docs/security.md @@ -94,4 +94,4 @@ circe 4529 ( 7456) 2037 (1533) This attack is very effective in schemas with lots of numbers, causing ops/sec to be halved with a 33% increase in memory usage. -`zio-json` is resistant to a wide range of number based attacks because it uses a from-scratch number parser that will exit early when the number of bits of any number exceeds 128 bits, which can be customized by the system property `zio.json.number.bits`. +`zio-json` is resistant to a wide range of number based attacks because it uses a from-scratch number parser that will exit early when the number of bits of any number exceeds 256 bits. diff --git a/zio-json-golden/src/test/resources/golden/filteredgentype/FilteredGenType.json b/zio-json-golden/src/test/resources/golden/filteredgentype/FilteredGenType.json index cbff71868..f5eed5eb3 100644 --- a/zio-json-golden/src/test/resources/golden/filteredgentype/FilteredGenType.json +++ b/zio-json-golden/src/test/resources/golden/filteredgentype/FilteredGenType.json @@ -1,304 +1,304 @@ { "samples" : [ { - "a" : -6.807778209396064042484608633080144E+37 + "a" : -2.316566882566080907224842077045326E+76 }, { - "a" : 1.595098341748072691735235811828587E+38 + "a" : -4.569412983810212758054894223077703E+76 }, { - "a" : 1.459950313422007732470858735428470E+38 + "a" : 5.228989702464284595177704658722007E+76 }, { - "a" : 3.966890979309949040353919413508109E+37 + "a" : 2.548479930525632094981978203969745E+76 }, { - "a" : 1.233609631287946015973115353212111E+38 + "a" : 1.374041878513211278247992702527512E+76 }, { - "a" : 1.473182672254656075288635468340979E+38 + "a" : 1.366736473169616603716555534546644E+76 }, { - "a" : -4.370307886719313347409020693679809E+36 + "a" : 3.911179114719388819861885552560843E+76 }, { - "a" : 4.037946164963809325160258151515959E+37 + "a" : 1.944890681930929342312977988211045E+76 }, { - "a" : 9.654807468219847939517983223821157E+36 + "a" : 4.465885331138848906986116797747655E+76 }, { - "a" : 9.016039177425153953741714882797374E+37 + "a" : 3.006637389783933263445463075262541E+76 }, { - "a" : 7.146160681625873270982431039786890E+37 + "a" : -5.751649455858367893815313696913694E+76 }, { - "a" : 5.715520023941784610636805140974297E+37 + "a" : -1.784887552001718903374836951202540E+76 }, { - "a" : -4.963886209068154507584885110622067E+37 + "a" : -4.910947695145483225976445529943760E+76 }, { - "a" : 1.429642469253121453233931676540811E+38 + "a" : -1.457349100691995363983649450959469E+76 }, { - "a" : 1.347358173024022215551559165152039E+38 + "a" : -2.173117328253411917053167250124104E+76 }, { - "a" : -1.443197818206812225540268705601235E+38 + "a" : -3.816709640530119614187277451466508E+76 }, { - "a" : 1.652974714985098210216497615438388E+38 + "a" : -3.324016269398039518561448095600154E+76 }, { - "a" : 6.854881439246725349714811941975828E+37 + "a" : 2.133340242534658987737410780967371E+76 }, { - "a" : -1.440269453811987772460377566176060E+38 + "a" : 2.899391869007164686595655383666939E+75 }, { - "a" : 7.349647466961309857751700891214103E+37 + "a" : 1.289089776333850543053015914013214E+75 }, { - "a" : 1.442405212658813209066752593353578E+38 + "a" : -3.245057840870445841628197448179636E+75 }, { - "a" : 3.965771366281331473297193084703031E+37 + "a" : 4.164835378606767954973150783579233E+76 }, { - "a" : 6.269323508703350877363225990119655E+37 + "a" : -3.770560172944845271964280390072137E+76 }, { - "a" : -1.758914181069706042842822199462931E+37 + "a" : 5.112438700963875953568847807288529E+76 }, { - "a" : 1.223935114914264676851645426397511E+38 + "a" : -4.522549082517651900179843809145666E+76 }, { - "a" : -7.384928634462281384291440360868614E+37 + "a" : 4.412399638137604448664402635608744E+76 }, { - "a" : 1.693228930919227030554211724500322E+38 + "a" : 1.514016982543592890366775343872403E+76 }, { - "a" : 4.424974721701017780262797468032916E+37 + "a" : -8.440138198169034012113061607781643E+75 }, { - "a" : -1.303664216711215462163176389172558E+38 + "a" : 8.692134402815438942477068350566858E+75 }, { - "a" : 1.404704317795160461166424204194676E+37 + "a" : 1.784909366617606219860919821779445E+75 }, { - "a" : -1.329057724454004799664476545910425E+38 + "a" : -3.207891411756439559673859312030194E+76 }, { - "a" : -1.048356224872675570271942686628903E+38 + "a" : 4.617070445357631467547656360854253E+76 }, { - "a" : -6.290438873817500525528580887751261E+37 + "a" : 3.901654475403585079437303428377294E+75 }, { - "a" : 1.084524705805485052810712065828027E+38 + "a" : 1.809738335539952279801907689220288E+75 }, { - "a" : -7.550243566246841354983346269026909E+37 + "a" : 1.867508850719084820991569833616372E+76 }, { - "a" : 9.340651914763911687206557126706644E+37 + "a" : -4.487014217306435076482810842738905E+76 }, { - "a" : -2.480333693026775008020223168834843E+37 + "a" : -3.483434481661576170222377673573347E+76 }, { - "a" : -1.094510132373439403344708257152704E+38 + "a" : -3.867693189336099364999112808463955E+76 }, { - "a" : -6.786566488714105384835344334324745E+37 + "a" : 3.097928175532344917894163172264977E+76 }, { - "a" : 1.584399487952599918984676750023353E+37 + "a" : -5.057812661184695904653860723677742E+76 }, { - "a" : 5.128682031007228158346770228279368E+37 + "a" : 4.968641799647727075789350301886275E+75 }, { - "a" : 9.348340016921799959253942302421099E+37 + "a" : -1.496654796873222027485144992012494E+76 }, { - "a" : 7.030935368599146218716189376901462E+37 + "a" : -2.496076752945529018769889843804460E+76 }, { - "a" : 1.535594588509527431040574927018135E+38 + "a" : -3.067118696151703815163256919101265E+76 }, { - "a" : -4.934741325633573501964215791643639E+37 + "a" : 3.412873919590638540319289335182708E+76 }, { - "a" : 9.682312857134232428245162228518594E+37 + "a" : 1.480433219806823809604660703733397E+76 }, { - "a" : -2.614986691569376906824897939793061E+37 + "a" : -6.339658483521043521286676489621022E+75 }, { - "a" : -1.014394404488501605028181495043210E+38 + "a" : -4.114346648557266778860963307677252E+76 }, { - "a" : 5.488115260327267108034268022746715E+37 + "a" : 8.097697147044163995162055017419827E+75 }, { - "a" : 1.126087665079675493640367522377595E+38 + "a" : 2.214651974619448279825666523589333E+76 }, { - "a" : -1.039540496026219616948722255179099E+38 + "a" : -3.574516354020052112075424495657422E+76 }, { - "a" : -9.140747409014218126917907727764322E+37 + "a" : 1.104360909238067990458557751532528E+76 }, { - "a" : -4.824125603843691307227014286009879E+37 + "a" : 1.399684847297342561050267725918531E+76 }, { - "a" : -8.145094645621006028171195553277435E+37 + "a" : 2.502156861243295972203472905374441E+76 }, { - "a" : -5.626674097898124181091040103617824E+37 + "a" : -1.604836447654289039403981953467744E+76 }, { - "a" : 9.103992673978668331401073051758253E+37 + "a" : 1.658811782314510920715101305263320E+76 }, { - "a" : -6.434701867932310680029743191675950E+37 + "a" : 5.139354255763948897207867902940651E+76 }, { - "a" : 1.544388056858114009506299836014695E+37 + "a" : 3.575018689737287736972267117825426E+76 }, { - "a" : -1.265122848783281340816555691730657E+38 + "a" : 5.210629430128216788313314937720483E+76 }, { - "a" : -1.038801113752491268000223564191006E+38 + "a" : 2.554485802500375585530452513390781E+76 }, { - "a" : 1.005113855834286665723509372286190E+38 + "a" : 5.272520867084473865402574088199686E+76 }, { - "a" : -1.532637041640666300354819845360303E+38 + "a" : 2.947116531100159457188114248938062E+76 }, { - "a" : 1.460152591686346812838507724273444E+37 + "a" : 5.606673728406112706411336596318572E+76 }, { - "a" : -1.807215480356152323767541674888201E+37 + "a" : -2.738195022969698014068611960296754E+76 }, { - "a" : 1.130164367464931673447884683370859E+38 + "a" : -4.988632907332175914225601107044307E+76 }, { - "a" : -2.206605703514989373349781667972389E+37 + "a" : 4.333006311262914052378138242825969E+76 }, { - "a" : -7.335310305765769880508310438610933E+37 + "a" : 1.811966148571775577237795257899742E+76 }, { - "a" : -9.032060789190847023210378392783773E+37 + "a" : 3.532034121631590266281611826625215E+76 }, { - "a" : -1.434866879379575685137940146522389E+38 + "a" : -3.030563670404741512818043914917342E+76 }, { - "a" : -8.537143289319745801149293653774310E+37 + "a" : 3.296229785246419707095000587083274E+76 }, { - "a" : -7.189751213495862580473152406825669E+37 + "a" : -2.181511118190992226401258245868472E+75 }, { - "a" : -1.116003336173084398830455517338484E+38 + "a" : -5.248524012170844440886915873124918E+76 }, { - "a" : 4.350602216631428056662100784595331E+37 + "a" : -2.710770101040726300947437430936575E+76 }, { - "a" : -4.200580225109217264680432341389498E+37 + "a" : -1.943466455385574430218910331374480E+76 }, { - "a" : 1.519464406997130639901449647551254E+38 + "a" : -1.374906724157606925294570143297134E+76 }, { - "a" : 1.605847304967111250934189072078944E+38 + "a" : 4.043622418717921248146223721034532E+76 }, { - "a" : -1.537207767079682775719785647758733E+38 + "a" : 2.647781320119073554660652286568971E+76 }, { - "a" : -1.279659726955884472452164873447049E+38 + "a" : 4.785326654831060667212354593166055E+76 }, { - "a" : 6.508277213006463765700394170604852E+37 + "a" : -2.393552079769908150471681607119720E+76 }, { - "a" : 1.283817488073224657514525683762202E+38 + "a" : -5.137463583987553425511474469196396E+74 }, { - "a" : 1.339367218367240868953919097436571E+38 + "a" : 1.067970096951755185859241750338926E+76 }, { - "a" : -1.682265900214633524445901815287998E+37 + "a" : 4.462068429365855045664774983601338E+76 }, { - "a" : 1.206863685039632397166752202964046E+38 + "a" : 4.632622188172519290610657636467222E+76 }, { - "a" : -1.084738675922968416633463809761249E+38 + "a" : 2.461239593255391819307695081683695E+76 }, { - "a" : 5.657051930790282199429825000570743E+37 + "a" : 1.729396380088878041781707741829649E+76 }, { - "a" : -8.295559405291592878379291027574690E+36 + "a" : 4.211133948578506902750612745348381E+76 }, { - "a" : 1.759196447669202351875403477421470E+37 + "a" : 4.588026434593706190135184314006573E+76 }, { - "a" : 1.469253810134078897550508376470893E+38 + "a" : 1.463113899048768702370675156356965E+76 }, { - "a" : 4.042754662729660664430342317410895E+37 + "a" : -6.211105372002631227089153408882691E+75 }, { - "a" : -2.203950127770040611464116843938096E+37 + "a" : -1.067219487693268067286110477325699E+76 }, { - "a" : 1.510320473631250916474309414975180E+38 + "a" : -3.186432400285580393171287587355880E+76 }, { - "a" : -7.763024290460987157407648151549977E+37 + "a" : -1.698453401490616715439518694710733E+76 }, { - "a" : 2.621985659558485942857886646450931E+37 + "a" : -4.746614822847365827886773917395646E+76 }, { - "a" : -7.736585181363804829301628996321853E+37 + "a" : 5.405374454168044475426989745354982E+76 }, { - "a" : -6.009164305461593191654380749583999E+37 + "a" : 3.493241979655892720402456230475866E+76 }, { - "a" : -9.519320687479863998450437188934807E+37 + "a" : -4.923252250897307532574828650812151E+76 }, { - "a" : -3.199912116712397916450907359672628E+37 + "a" : -4.011060043301704192745191895787696E+76 }, { - "a" : 1.050603568467569482115229536968020E+38 + "a" : -5.689109292584424095652541096534457E+76 }, { - "a" : -1.259168689102438672889911917677648E+38 + "a" : 1.636983807742317199740108642526352E+76 }, { - "a" : -1.203951555022847927293048112388223E+38 + "a" : -2.083099272612476220093157442600918E+75 } ] } \ No newline at end of file diff --git a/zio-json-golden/src/test/scala/zio/json/golden/GoldenSpec.scala b/zio-json-golden/src/test/scala/zio/json/golden/GoldenSpec.scala index 89eb98d92..4f609e433 100644 --- a/zio-json-golden/src/test/scala/zio/json/golden/GoldenSpec.scala +++ b/zio-json-golden/src/test/scala/zio/json/golden/GoldenSpec.scala @@ -40,9 +40,9 @@ object GoldenSpec extends ZIOSpecDefault { */ val genBigDecimal: Gen[Any, java.math.BigDecimal] = Gen - .bigDecimal((BigDecimal(2).pow(128) - 1) * -1, BigDecimal(2).pow(128) - 1) + .bigDecimal((BigDecimal(2).pow(256) - 1) * -1, BigDecimal(2).pow(256) - 1) .map(_.bigDecimal) - .filter(_.toBigInteger.bitLength < 128) + .filter(_.toBigInteger.bitLength < 256) genBigDecimal.map(FilteredGenType.apply) } diff --git a/zio-json/js/src/main/scala/zio/json/internal/SafeNumbers.scala b/zio-json/js/src/main/scala/zio/json/internal/SafeNumbers.scala index 617538ddc..513f19795 100644 --- a/zio-json/js/src/main/scala/zio/json/internal/SafeNumbers.scala +++ b/zio-json/js/src/main/scala/zio/json/internal/SafeNumbers.scala @@ -35,7 +35,7 @@ import java.util.UUID * "1.000e-5", which is useful in cases where the trailing zeros denote measurement accuracy. * * `BigInteger`, `BigDecimal`, `Float` and `Double` have a configurable bit limit on the size of the significand, to - * avoid OOM style attacks, which is 128 bits by default. + * avoid OOM style attacks, which is 256 bits by default. * * Results are contained in a specialisation of Option that avoids boxing. */ @@ -58,19 +58,19 @@ object SafeNumbers { try LongSome(UnsafeNumbers.long(num)) catch { case _: UnexpectedEnd | UnsafeNumber => LongNone } - def bigInteger(num: String, max_bits: Int = 128): Option[java.math.BigInteger] = + def bigInteger(num: String, max_bits: Int = 256): Option[java.math.BigInteger] = try Some(UnsafeNumbers.bigInteger(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => None } - def float(num: String, max_bits: Int = 128): FloatOption = + def float(num: String, max_bits: Int = 256): FloatOption = try FloatSome(UnsafeNumbers.float(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => FloatNone } - def double(num: String, max_bits: Int = 128): DoubleOption = + def double(num: String, max_bits: Int = 256): DoubleOption = try DoubleSome(UnsafeNumbers.double(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => DoubleNone } - def bigDecimal(num: String, max_bits: Int = 128): Option[java.math.BigDecimal] = + def bigDecimal(num: String, max_bits: Int = 256): Option[java.math.BigDecimal] = try Some(UnsafeNumbers.bigDecimal(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => None } diff --git a/zio-json/js/src/main/scala/zio/json/internal/UnsafeNumbers.scala b/zio-json/js/src/main/scala/zio/json/internal/UnsafeNumbers.scala index b358ffb6b..70bb1b778 100644 --- a/zio-json/js/src/main/scala/zio/json/internal/UnsafeNumbers.scala +++ b/zio-json/js/src/main/scala/zio/json/internal/UnsafeNumbers.scala @@ -254,17 +254,14 @@ object UnsafeNumbers { var current = if (consume) in.readChar().toInt else in.nextNonWhitespace().toInt - if (current == 'N') { + val negate = current == '-' + if (negate) current = in.readChar().toInt + else if (current == 'N') { readAll(in, "aN", consume) return Float.NaN } - val negate = current == '-' - if (negate) current = in.readChar().toInt if (current == 'I' || current == '+') { - if (current == '+') { - current = in.readChar().toInt - if (current != 'I') throw UnsafeNumber - } + if (current == '+' && in.readChar() != 'I') throw UnsafeNumber readAll(in, "nfinity", consume) return if (negate) Float.NegativeInfinity else Float.PositiveInfinity } @@ -327,7 +324,7 @@ object UnsafeNumbers { } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { - var x: Float = + var x = if (e10 == 0) loM10.toFloat else { if (loM10 < 4294967296L && e10 >= loDigits - 23 && e10 <= 19 - loDigits) { @@ -339,7 +336,7 @@ object UnsafeNumbers { if (negate) x = -x return x } - toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).floatValue() + toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).floatValue } // Based on the 'Moderate Path' algorithm from the awesome library of Alexander Huszagh: https://github.com/Alexhuszagh/rust-lexical @@ -372,7 +369,7 @@ object UnsafeNumbers { else if (e2 >= 105) 0x7f800000 else e2 + 150 << 23 | mf & 0x7fffff } - else java.math.BigDecimal.valueOf(m10, -e10).floatValue() + else java.math.BigDecimal.valueOf(m10, -e10).floatValue } def double(num: String, max_bits: Int): Double = @@ -382,17 +379,14 @@ object UnsafeNumbers { var current = if (consume) in.readChar().toInt else in.nextNonWhitespace().toInt - if (current == 'N') { + val negate = current == '-' + if (negate) current = in.readChar().toInt + else if (current == 'N') { readAll(in, "aN", consume) return Double.NaN } - val negate = current == '-' - if (negate) current = in.readChar().toInt if (current == 'I' || current == '+') { - if (current == '+') { - current = in.readChar().toInt - if (current != 'I') throw UnsafeNumber - } + if (current == '+' && in.readChar() != 'I') throw UnsafeNumber readAll(in, "nfinity", consume) return if (negate) Double.NegativeInfinity else Double.PositiveInfinity } @@ -455,7 +449,7 @@ object UnsafeNumbers { } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { - var x: Double = + var x = if (e10 == 0) loM10.toDouble else { if (loM10 < 4503599627370496L && e10 >= -22 && e10 <= 38 - loDigits) { @@ -471,7 +465,7 @@ object UnsafeNumbers { if (negate) x = -x return x } - toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).doubleValue() + toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).doubleValue } // Based on the 'Moderate Path' algorithm from the awesome library of Alexander Huszagh: https://github.com/Alexhuszagh/rust-lexical @@ -504,7 +498,7 @@ object UnsafeNumbers { else if (e2 >= 972) 0x7ff0000000000000L else (e2 + 1075).toLong << 52 | m2 & 0xfffffffffffffL } - else java.math.BigDecimal.valueOf(m10, -e10).doubleValue() + else java.math.BigDecimal.valueOf(m10, -e10).doubleValue } @noinline private[this] def readAll(in: OneCharReader, s: String, consume: Boolean): Unit = { diff --git a/zio-json/jvm/src/jmh/scala/zio/json/internal/SafeNumbersBenchmarks.scala b/zio-json/jvm/src/jmh/scala/zio/json/internal/SafeNumbersBenchmarks.scala index 18bf3a5a0..345b8afa0 100644 --- a/zio-json/jvm/src/jmh/scala/zio/json/internal/SafeNumbersBenchmarks.scala +++ b/zio-json/jvm/src/jmh/scala/zio/json/internal/SafeNumbersBenchmarks.scala @@ -115,7 +115,7 @@ class SafeNumbersBenchFloat { @Benchmark def decodeFommilUnsafeValid(): Array[Float] = - valids.map(UnsafeNumbers.float(_, 128)) + valids.map(UnsafeNumbers.float(_, 256)) @Benchmark def decodeStdlibInvalid(): Array[FloatOption] = invalids.map(stdlib) @@ -182,7 +182,7 @@ class SafeNumbersBenchBigDecimal { @Benchmark def decodeFommilUnsafeValid(): Array[java.math.BigDecimal] = - valids.map(UnsafeNumbers.bigDecimal(_, 128)) + valids.map(UnsafeNumbers.bigDecimal(_, 256)) @Benchmark def decodeStdlibInvalid(): Array[Option[java.math.BigDecimal]] = diff --git a/zio-json/jvm/src/main/scala/zio/json/internal/SafeNumbers.scala b/zio-json/jvm/src/main/scala/zio/json/internal/SafeNumbers.scala index 6e27b91c6..e6a72b054 100644 --- a/zio-json/jvm/src/main/scala/zio/json/internal/SafeNumbers.scala +++ b/zio-json/jvm/src/main/scala/zio/json/internal/SafeNumbers.scala @@ -35,7 +35,7 @@ import java.util.UUID * "1.000e-5", which is useful in cases where the trailing zeros denote measurement accuracy. * * `BigInteger`, `BigDecimal`, `Float` and `Double` have a configurable bit limit on the size of the significand, to - * avoid OOM style attacks, which is 128 bits by default. + * avoid OOM style attacks, which is 256 bits by default. * * Results are contained in a specialisation of Option that avoids boxing. */ @@ -58,19 +58,19 @@ object SafeNumbers { try LongSome(UnsafeNumbers.long(num)) catch { case _: UnexpectedEnd | UnsafeNumber => LongNone } - def bigInteger(num: String, max_bits: Int = 128): Option[java.math.BigInteger] = + def bigInteger(num: String, max_bits: Int = 256): Option[java.math.BigInteger] = try Some(UnsafeNumbers.bigInteger(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => None } - def float(num: String, max_bits: Int = 128): FloatOption = + def float(num: String, max_bits: Int = 256): FloatOption = try FloatSome(UnsafeNumbers.float(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => FloatNone } - def double(num: String, max_bits: Int = 128): DoubleOption = + def double(num: String, max_bits: Int = 256): DoubleOption = try DoubleSome(UnsafeNumbers.double(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => DoubleNone } - def bigDecimal(num: String, max_bits: Int = 128): Option[java.math.BigDecimal] = + def bigDecimal(num: String, max_bits: Int = 256): Option[java.math.BigDecimal] = try Some(UnsafeNumbers.bigDecimal(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => None } diff --git a/zio-json/jvm/src/main/scala/zio/json/internal/UnsafeNumbers.scala b/zio-json/jvm/src/main/scala/zio/json/internal/UnsafeNumbers.scala index a41908157..ce269ae7a 100644 --- a/zio-json/jvm/src/main/scala/zio/json/internal/UnsafeNumbers.scala +++ b/zio-json/jvm/src/main/scala/zio/json/internal/UnsafeNumbers.scala @@ -254,17 +254,14 @@ object UnsafeNumbers { var current = if (consume) in.readChar().toInt else in.nextNonWhitespace().toInt - if (current == 'N') { + val negate = current == '-' + if (negate) current = in.readChar().toInt + else if (current == 'N') { readAll(in, "aN", consume) return Float.NaN } - val negate = current == '-' - if (negate) current = in.readChar().toInt if (current == 'I' || current == '+') { - if (current == '+') { - current = in.readChar().toInt - if (current != 'I') throw UnsafeNumber - } + if (current == '+' && in.readChar() != 'I') throw UnsafeNumber readAll(in, "nfinity", consume) return if (negate) Float.NegativeInfinity else Float.PositiveInfinity } @@ -327,7 +324,7 @@ object UnsafeNumbers { } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { - var x: Float = + var x = if (e10 == 0) loM10.toFloat else { if (loM10 < 4294967296L && e10 >= loDigits - 23 && e10 <= 19 - loDigits) { @@ -339,7 +336,7 @@ object UnsafeNumbers { if (negate) x = -x return x } - toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).floatValue() + toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).floatValue } // Based on the 'Moderate Path' algorithm from the awesome library of Alexander Huszagh: https://github.com/Alexhuszagh/rust-lexical @@ -372,7 +369,7 @@ object UnsafeNumbers { else if (e2 >= 105) 0x7f800000 else e2 + 150 << 23 | mf & 0x7fffff } - else java.math.BigDecimal.valueOf(m10, -e10).floatValue() + else java.math.BigDecimal.valueOf(m10, -e10).floatValue } def double(num: String, max_bits: Int): Double = @@ -382,17 +379,14 @@ object UnsafeNumbers { var current = if (consume) in.readChar().toInt else in.nextNonWhitespace().toInt - if (current == 'N') { + val negate = current == '-' + if (negate) current = in.readChar().toInt + else if (current == 'N') { readAll(in, "aN", consume) return Double.NaN } - val negate = current == '-' - if (negate) current = in.readChar().toInt if (current == 'I' || current == '+') { - if (current == '+') { - current = in.readChar().toInt - if (current != 'I') throw UnsafeNumber - } + if (current == '+' && in.readChar() != 'I') throw UnsafeNumber readAll(in, "nfinity", consume) return if (negate) Double.NegativeInfinity else Double.PositiveInfinity } @@ -455,7 +449,7 @@ object UnsafeNumbers { } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { - var x: Double = + var x = if (e10 == 0) loM10.toDouble else { if (loM10 < 4503599627370496L && e10 >= -22 && e10 <= 38 - loDigits) { @@ -471,7 +465,7 @@ object UnsafeNumbers { if (negate) x = -x return x } - toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).doubleValue() + toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).doubleValue } // Based on the 'Moderate Path' algorithm from the awesome library of Alexander Huszagh: https://github.com/Alexhuszagh/rust-lexical @@ -504,7 +498,7 @@ object UnsafeNumbers { else if (e2 >= 972) 0x7ff0000000000000L else (e2 + 1075).toLong << 52 | m2 & 0xfffffffffffffL } - else java.math.BigDecimal.valueOf(m10, -e10).doubleValue() + else java.math.BigDecimal.valueOf(m10, -e10).doubleValue } @noinline private[this] def readAll(in: OneCharReader, s: String, consume: Boolean): Unit = { diff --git a/zio-json/jvm/src/test/scala/zio/json/data/geojson/GeoJSON.scala b/zio-json/jvm/src/test/scala/zio/json/data/geojson/GeoJSON.scala index 9cbca8d9b..b827fd66b 100644 --- a/zio-json/jvm/src/test/scala/zio/json/data/geojson/GeoJSON.scala +++ b/zio-json/jvm/src/test/scala/zio/json/data/geojson/GeoJSON.scala @@ -156,7 +156,7 @@ package handrolled { js match { case Json.Arr(chunk) if chunk.length == 2 && chunk(0).isInstanceOf[Json.Num] && chunk(1).isInstanceOf[Json.Num] => - (chunk(0).asInstanceOf[Json.Num].value.doubleValue(), chunk(1).asInstanceOf[Json.Num].value.doubleValue()) + (chunk(0).asInstanceOf[Json.Num].value.doubleValue, chunk(1).asInstanceOf[Json.Num].value.doubleValue) case _ => Lexer.error("expected coordinates", trace) } def coordinates1( diff --git a/zio-json/native/src/main/scala/zio/json/internal/SafeNumbers.scala b/zio-json/native/src/main/scala/zio/json/internal/SafeNumbers.scala index 1abc290d7..547918b9e 100644 --- a/zio-json/native/src/main/scala/zio/json/internal/SafeNumbers.scala +++ b/zio-json/native/src/main/scala/zio/json/internal/SafeNumbers.scala @@ -35,7 +35,7 @@ import java.util.UUID * "1.000e-5", which is useful in cases where the trailing zeros denote measurement accuracy. * * `BigInteger`, `BigDecimal`, `Float` and `Double` have a configurable bit limit on the size of the significand, to - * avoid OOM style attacks, which is 128 bits by default. + * avoid OOM style attacks, which is 256 bits by default. * * Results are contained in a specialisation of Option that avoids boxing. */ @@ -58,19 +58,19 @@ object SafeNumbers { try LongSome(UnsafeNumbers.long(num)) catch { case _: UnexpectedEnd | UnsafeNumber => LongNone } - def bigInteger(num: String, max_bits: Int = 128): Option[java.math.BigInteger] = + def bigInteger(num: String, max_bits: Int = 256): Option[java.math.BigInteger] = try Some(UnsafeNumbers.bigInteger(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => None } - def float(num: String, max_bits: Int = 128): FloatOption = + def float(num: String, max_bits: Int = 256): FloatOption = try FloatSome(UnsafeNumbers.float(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => FloatNone } - def double(num: String, max_bits: Int = 128): DoubleOption = + def double(num: String, max_bits: Int = 256): DoubleOption = try DoubleSome(UnsafeNumbers.double(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => DoubleNone } - def bigDecimal(num: String, max_bits: Int = 128): Option[java.math.BigDecimal] = + def bigDecimal(num: String, max_bits: Int = 256): Option[java.math.BigDecimal] = try Some(UnsafeNumbers.bigDecimal(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => None } diff --git a/zio-json/native/src/main/scala/zio/json/internal/UnsafeNumbers.scala b/zio-json/native/src/main/scala/zio/json/internal/UnsafeNumbers.scala index a41908157..ce269ae7a 100644 --- a/zio-json/native/src/main/scala/zio/json/internal/UnsafeNumbers.scala +++ b/zio-json/native/src/main/scala/zio/json/internal/UnsafeNumbers.scala @@ -254,17 +254,14 @@ object UnsafeNumbers { var current = if (consume) in.readChar().toInt else in.nextNonWhitespace().toInt - if (current == 'N') { + val negate = current == '-' + if (negate) current = in.readChar().toInt + else if (current == 'N') { readAll(in, "aN", consume) return Float.NaN } - val negate = current == '-' - if (negate) current = in.readChar().toInt if (current == 'I' || current == '+') { - if (current == '+') { - current = in.readChar().toInt - if (current != 'I') throw UnsafeNumber - } + if (current == '+' && in.readChar() != 'I') throw UnsafeNumber readAll(in, "nfinity", consume) return if (negate) Float.NegativeInfinity else Float.PositiveInfinity } @@ -327,7 +324,7 @@ object UnsafeNumbers { } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { - var x: Float = + var x = if (e10 == 0) loM10.toFloat else { if (loM10 < 4294967296L && e10 >= loDigits - 23 && e10 <= 19 - loDigits) { @@ -339,7 +336,7 @@ object UnsafeNumbers { if (negate) x = -x return x } - toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).floatValue() + toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).floatValue } // Based on the 'Moderate Path' algorithm from the awesome library of Alexander Huszagh: https://github.com/Alexhuszagh/rust-lexical @@ -372,7 +369,7 @@ object UnsafeNumbers { else if (e2 >= 105) 0x7f800000 else e2 + 150 << 23 | mf & 0x7fffff } - else java.math.BigDecimal.valueOf(m10, -e10).floatValue() + else java.math.BigDecimal.valueOf(m10, -e10).floatValue } def double(num: String, max_bits: Int): Double = @@ -382,17 +379,14 @@ object UnsafeNumbers { var current = if (consume) in.readChar().toInt else in.nextNonWhitespace().toInt - if (current == 'N') { + val negate = current == '-' + if (negate) current = in.readChar().toInt + else if (current == 'N') { readAll(in, "aN", consume) return Double.NaN } - val negate = current == '-' - if (negate) current = in.readChar().toInt if (current == 'I' || current == '+') { - if (current == '+') { - current = in.readChar().toInt - if (current != 'I') throw UnsafeNumber - } + if (current == '+' && in.readChar() != 'I') throw UnsafeNumber readAll(in, "nfinity", consume) return if (negate) Double.NegativeInfinity else Double.PositiveInfinity } @@ -455,7 +449,7 @@ object UnsafeNumbers { } if (consume && current != -1) throw UnsafeNumber if (hiM10 eq null) { - var x: Double = + var x = if (e10 == 0) loM10.toDouble else { if (loM10 < 4503599627370496L && e10 >= -22 && e10 <= 38 - loDigits) { @@ -471,7 +465,7 @@ object UnsafeNumbers { if (negate) x = -x return x } - toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).doubleValue() + toBigDecimal(hiM10, loM10, loDigits, e10, max_bits, negate).doubleValue } // Based on the 'Moderate Path' algorithm from the awesome library of Alexander Huszagh: https://github.com/Alexhuszagh/rust-lexical @@ -504,7 +498,7 @@ object UnsafeNumbers { else if (e2 >= 972) 0x7ff0000000000000L else (e2 + 1075).toLong << 52 | m2 & 0xfffffffffffffL } - else java.math.BigDecimal.valueOf(m10, -e10).doubleValue() + else java.math.BigDecimal.valueOf(m10, -e10).doubleValue } @noinline private[this] def readAll(in: OneCharReader, s: String, consume: Boolean): Unit = { diff --git a/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala b/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala index aa4988dae..5628ec091 100644 --- a/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala +++ b/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala @@ -308,7 +308,7 @@ object Lexer { in.retract() i } catch { - case UnsafeNumbers.UnsafeNumber => error(s"expected a $NumberMaxBits bit BigInteger", trace) + case UnsafeNumbers.UnsafeNumber => error(s"expected a $NumberMaxBits-bit BigInteger", trace) } def float(trace: List[JsonError], in: RetractReader): Float = @@ -335,7 +335,7 @@ object Lexer { in.retract() i } catch { - case UnsafeNumbers.UnsafeNumber => error(s"expected a $NumberMaxBits BigDecimal", trace) + case UnsafeNumbers.UnsafeNumber => error(s"expected a BigDecimal with $NumberMaxBits-bit mantissa", trace) } @inline def char(trace: List[JsonError], in: OneCharReader, c: Char): Unit = { @@ -343,6 +343,7 @@ object Lexer { if (got != c) error(s"'$c'", got, trace) } + // FIXME: remove on next major version release @inline def charOnly(trace: List[JsonError], in: OneCharReader, c: Char): Unit = { val got = in.readChar() if (got != c) error(s"'$c'", got, trace) diff --git a/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala b/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala index 60f002c1a..a785de23e 100644 --- a/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala @@ -40,7 +40,7 @@ object CodecSpec extends ZIOSpecDefault { "170141183460469231731687303715884105728489465165484668486513574864654818964653168465316546851" .fromJson[java.math.BigInteger] )( - isLeft(equalTo("(expected a 256 bit BigInteger)")) + isLeft(equalTo("(expected a 256-bit BigInteger)")) ) }, test("java.util.Currency") { diff --git a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala index ba61bc278..7fbbc4f1b 100644 --- a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala @@ -106,6 +106,20 @@ object DecoderSpec extends ZIOSpecDefault { assertTrue("\"-Infinity\"".fromJson[BigDecimal].isLeft) && assertTrue("\"NaN\"".fromJson[BigDecimal].isLeft) }, + test("BigDecimal from JSON AST") { + assert("13.38885989999999992505763657391071319580078125".fromJson[Json])( + isRight(equalTo(Json.Num(BigDecimal("13.38885989999999992505763657391071319580078125")))) + ) + }, + test("BigDecimal too large") { + // this big integer consumes more than 256 bits + assert( + "170141183460469231731687303715884105728489465165484668486513574864654818964653168465316546851" + .fromJson[BigDecimal] + )( + isLeft(equalTo("(expected a BigDecimal with 256-bit mantissa)")) + ) + }, test("BigInteger") { assert("170141183460469231731687303715884105728".fromJson[BigInteger])( isRight(equalTo(new BigInteger("170141183460469231731687303715884105728"))) @@ -124,7 +138,7 @@ object DecoderSpec extends ZIOSpecDefault { "170141183460469231731687303715884105728489465165484668486513574864654818964653168465316546851" .fromJson[java.math.BigInteger] )( - isLeft(equalTo("(expected a 256 bit BigInteger)")) + isLeft(equalTo("(expected a 256-bit BigInteger)")) ) }, test("collections") { diff --git a/zio-json/shared/src/test/scala/zio/json/EncoderSpec.scala b/zio-json/shared/src/test/scala/zio/json/EncoderSpec.scala index d7421debb..278512cd2 100644 --- a/zio-json/shared/src/test/scala/zio/json/EncoderSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/EncoderSpec.scala @@ -15,13 +15,13 @@ object EncoderSpec extends ZIOSpecDefault { val spec: Spec[Environment, Any] = suite("Encoder")( suite("toJson")( + test("strings") { + assert("hello world".toJson)(equalTo("\"hello world\"")) && + assert("hello\nworld".toJson)(equalTo("\"hello\\nworld\"")) && + assert("hello\rworld".toJson)(equalTo("\"hello\\rworld\"")) && + assert("hello\u0000world".toJson)(equalTo("\"hello\\u0000world\"")) + }, suite("primitives")( - test("strings") { - assert("hello world".toJson)(equalTo("\"hello world\"")) && - assert("hello\nworld".toJson)(equalTo("\"hello\\nworld\"")) && - assert("hello\rworld".toJson)(equalTo("\"hello\\rworld\"")) && - assert("hello\u0000world".toJson)(equalTo("\"hello\\u0000world\"")) - }, test("boolean") { assert(true.toJson)(equalTo("true")) && assert(false.toJson)(equalTo("false")) @@ -30,6 +30,63 @@ object EncoderSpec extends ZIOSpecDefault { assert('c'.toJson)(equalTo("\"c\"")) && assert(Symbol("c").toJson)(equalTo("\"c\"")) }, + test("byte") { + assert((0: Short).toJson)(equalTo("0")) && + assert((1: Short).toJson)(equalTo("1")) && + assert((12: Short).toJson)(equalTo("12")) && + assert((123: Short).toJson)(equalTo("123")) && + assert((127: Short).toJson)(equalTo("127")) && + assert((-128: Short).toJson)(equalTo("-128")) + }, + test("short") { + assert((0: Short).toJson)(equalTo("0")) && + assert((1: Short).toJson)(equalTo("1")) && + assert((12: Short).toJson)(equalTo("12")) && + assert((123: Short).toJson)(equalTo("123")) && + assert((1234: Short).toJson)(equalTo("1234")) && + assert((12345: Short).toJson)(equalTo("12345")) && + assert((32767: Short).toJson)(equalTo("32767")) && + assert((-32768: Short).toJson)(equalTo("-32768")) + }, + test("int") { + assert(0.toJson)(equalTo("0")) && + assert(1.toJson)(equalTo("1")) && + assert(12.toJson)(equalTo("12")) && + assert(123.toJson)(equalTo("123")) && + assert(1234.toJson)(equalTo("1234")) && + assert(12345.toJson)(equalTo("12345")) && + assert(123456.toJson)(equalTo("123456")) && + assert(1234567.toJson)(equalTo("1234567")) && + assert(12345678.toJson)(equalTo("12345678")) && + assert(123456789.toJson)(equalTo("123456789")) && + assert(1234567890.toJson)(equalTo("1234567890")) && + assert(2147483647.toJson)(equalTo("2147483647")) && + assert(-2147483648.toJson)(equalTo("-2147483648")) + }, + test("long") { + assert(0L.toJson)(equalTo("0")) && + assert(1L.toJson)(equalTo("1")) && + assert(12L.toJson)(equalTo("12")) && + assert(123L.toJson)(equalTo("123")) && + assert(1234L.toJson)(equalTo("1234")) && + assert(12345L.toJson)(equalTo("12345")) && + assert(123456L.toJson)(equalTo("123456")) && + assert(1234567L.toJson)(equalTo("1234567")) && + assert(12345678L.toJson)(equalTo("12345678")) && + assert(123456789L.toJson)(equalTo("123456789")) && + assert(1234567890L.toJson)(equalTo("1234567890")) && + assert(12345678901L.toJson)(equalTo("12345678901")) && + assert(123456789012L.toJson)(equalTo("123456789012")) && + assert(1234567890123L.toJson)(equalTo("1234567890123")) && + assert(12345678901234L.toJson)(equalTo("12345678901234")) && + assert(123456789012345L.toJson)(equalTo("123456789012345")) && + assert(1234567890123456L.toJson)(equalTo("1234567890123456")) && + assert(12345678901234567L.toJson)(equalTo("12345678901234567")) && + assert(123456789012345678L.toJson)(equalTo("123456789012345678")) && + assert(1234567890123456789L.toJson)(equalTo("1234567890123456789")) && + assert(9223372036854775807L.toJson)(equalTo("9223372036854775807")) && + assert(-9223372036854775808L.toJson)(equalTo("-9223372036854775808")) + }, test("float") { assert(Float.NaN.toJson)(equalTo("\"NaN\"")) && assert(Float.PositiveInfinity.toJson)(equalTo("\"Infinity\"")) && @@ -137,6 +194,7 @@ object EncoderSpec extends ZIOSpecDefault { assert(7.1202363472230444e-307d.toJson)(equalTo("7.120236347223045E-307")) && assert(3.67301024534615e16d.toJson)(equalTo("3.67301024534615E16")) && assert(5.9604644775390625e-8d.toJson)(equalTo("5.960464477539063E-8")) && + assert(5.829003601188985e15d.toJson)(equalTo("5.829003601188985E15")) && assert(1.0e-322d.toJson)(equalTo("9.9E-323")) && // 20 * 2 ^ -1074 == 9.88... * 10 ^ -323 assert(5.0e-324d.toJson)(equalTo("4.9E-324")) && // 1 * 2 ^ -1074 == 4.94... * 10 ^ -324 assert(1.0e23d.toJson)( diff --git a/zio-json/shared/src/test/scala/zio/json/Gens.scala b/zio-json/shared/src/test/scala/zio/json/Gens.scala index e9d5c4ea3..417956559 100644 --- a/zio-json/shared/src/test/scala/zio/json/Gens.scala +++ b/zio-json/shared/src/test/scala/zio/json/Gens.scala @@ -9,15 +9,15 @@ import scala.util.Try object Gens { val genBigInteger = Gen - .bigInt((BigInt(2).pow(128) - 1) * -1, BigInt(2).pow(128) - 1) + .bigInt((BigInt(2).pow(256) - 1) * -1, BigInt(2).pow(256) - 1) .map(_.bigInteger) - .filter(_.bitLength < 128) + .filter(_.bitLength < 256) val genBigDecimal = Gen - .bigDecimal((BigDecimal(2).pow(128) - 1) * -1, BigDecimal(2).pow(128) - 1) + .bigDecimal((BigDecimal(2).pow(256) - 1) * -1, BigDecimal(2).pow(256) - 1) .map(_.bigDecimal) - .filter(_.unscaledValue.bitLength < 128) + .filter(_.unscaledValue.bitLength < 256) val genUsAsciiString = Gen.string(Gen.oneOf(Gen.char('!', '~'))) diff --git a/zio-json/shared/src/test/scala/zio/json/internal/SafeNumbersSpec.scala b/zio-json/shared/src/test/scala/zio/json/internal/SafeNumbersSpec.scala index 149668481..078126fa8 100644 --- a/zio-json/shared/src/test/scala/zio/json/internal/SafeNumbersSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/internal/SafeNumbersSpec.scala @@ -114,7 +114,7 @@ object SafeNumbersSpec extends ZIOSpecDefault { check(Gen.long)(i => assert(SafeNumbers.double(i.toString))(equalTo(DoubleSome(i.toDouble)))) }, test("valid (from BigDecimal)") { - check(genBigDecimal)(i => assert(SafeNumbers.double(i.toString))(equalTo(DoubleSome(i.doubleValue())))) + check(genBigDecimal)(i => assert(SafeNumbers.double(i.toString))(equalTo(DoubleSome(i.doubleValue)))) }, test("invalid edge cases") { val inputs = List( @@ -277,13 +277,18 @@ object SafeNumbersSpec extends ZIOSpecDefault { } }, test("valid (from BigDecimal)") { - check(genBigDecimal)(i => assert(SafeNumbers.float(i.toString))(equalTo(FloatSome(i.floatValue())))) + check(genBigDecimal)(i => assert(SafeNumbers.float(i.toString))(equalTo(FloatSome(i.floatValue)))) }, test("invalid float (text)") { check(genAlphaLowerString)(s => assert(SafeNumbers.float(s))(equalTo(FloatNone))) } ), suite("Int")( + test("valid edge cases") { + val input = List("00", "01", "0000001", "-2147483648", "2147483647") + + check(Gen.fromIterable(input))(x => assert(SafeNumbers.int(x))(equalTo(IntSome(x.toInt)))) + }, test("valid") { check(Gen.int)(d => assert(SafeNumbers.int(d.toString))(equalTo(IntSome(d)))) }, @@ -311,7 +316,7 @@ object SafeNumbersSpec extends ZIOSpecDefault { ), suite("Long")( test("valid edge cases") { - val input = List("00", "01", "0000001", "-9223372036854775807", "9223372036854775806") + val input = List("00", "01", "0000001", "-9223372036854775808", "9223372036854775807") check(Gen.fromIterable(input))(x => assert(SafeNumbers.long(x))(equalTo(LongSome(x.toLong)))) },