Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteffen Kieß <Steffen.Kiess@ipvs.uni-stuttgart.de>2014-07-07 21:22:29 +0400
committerSteffen Kieß <Steffen.Kiess@ipvs.uni-stuttgart.de>2014-07-07 21:22:29 +0400
commitb005523cb17989ffc0d2f2662ce7200fb49803b1 (patch)
tree05301182fdb14931488bb702fcf1263488ca33d8
parent66b9914768e9b8ffe7c1eee0ae5d95b287b49a6c (diff)
Fix several issues in System.Json
- Fix detection of extra characters in JSON input - Fix detection of invalid negative numeric literals - Disallow leading zeros - Use "round-trip" format for floating point numbers - Serialize NaN and Infinity as strings - Use NumberFormatInfo.InvariantInfo for Convert.To*() - Use standard number parsing methods in ReadNumericLiteral() - Add test cases
-rw-r--r--mcs/class/System.Json/System.Json/JsonPrimitive.cs11
-rw-r--r--mcs/class/System.Json/System.Json/JsonValue.cs27
-rw-r--r--mcs/class/System.Json/Test/System.Json/JsonValueTest.cs140
-rw-r--r--mcs/class/System.ServiceModel.Web/System.Runtime.Serialization.Json/JavaScriptReader.cs105
4 files changed, 213 insertions, 70 deletions
diff --git a/mcs/class/System.Json/System.Json/JsonPrimitive.cs b/mcs/class/System.Json/System.Json/JsonPrimitive.cs
index 64518785b87..5d47eb4a4e6 100644
--- a/mcs/class/System.Json/System.Json/JsonPrimitive.cs
+++ b/mcs/class/System.Json/System.Json/JsonPrimitive.cs
@@ -163,7 +163,16 @@ namespace System.Json
return (string) value;
throw new NotImplementedException ("GetFormattedString from value type " + value.GetType ());
case JsonType.Number:
- return ((IFormattable) value).ToString ("G", NumberFormatInfo.InvariantInfo);
+ string s;
+ if (value is float || value is double)
+ // Use "round-trip" format
+ s = ((IFormattable) value).ToString ("R", NumberFormatInfo.InvariantInfo);
+ else
+ s = ((IFormattable) value).ToString ("G", NumberFormatInfo.InvariantInfo);
+ if (s == "NaN" || s == "Infinity" || s == "-Infinity")
+ return "\"" + s + "\"";
+ else
+ return s;
default:
throw new InvalidOperationException ();
}
diff --git a/mcs/class/System.Json/System.Json/JsonValue.cs b/mcs/class/System.Json/System.Json/JsonValue.cs
index 39722f5c169..d703edd5512 100644
--- a/mcs/class/System.Json/System.Json/JsonValue.cs
+++ b/mcs/class/System.Json/System.Json/JsonValue.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
@@ -328,70 +329,70 @@ namespace System.Json
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToBoolean (((JsonPrimitive) value).Value);
+ return Convert.ToBoolean (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator byte (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToByte (((JsonPrimitive) value).Value);
+ return Convert.ToByte (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator char (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToChar (((JsonPrimitive) value).Value);
+ return Convert.ToChar (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator decimal (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToDecimal (((JsonPrimitive) value).Value);
+ return Convert.ToDecimal (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator double (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToDouble (((JsonPrimitive) value).Value);
+ return Convert.ToDouble (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator float (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToSingle (((JsonPrimitive) value).Value);
+ return Convert.ToSingle (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator int (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToInt32 (((JsonPrimitive) value).Value);
+ return Convert.ToInt32 (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator long (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToInt64 (((JsonPrimitive) value).Value);
+ return Convert.ToInt64 (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator sbyte (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToSByte (((JsonPrimitive) value).Value);
+ return Convert.ToSByte (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator short (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToInt16 (((JsonPrimitive) value).Value);
+ return Convert.ToInt16 (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator string (JsonValue value)
@@ -405,21 +406,21 @@ namespace System.Json
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToUInt16 (((JsonPrimitive) value).Value);
+ return Convert.ToUInt16 (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator ulong (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToUInt64(((JsonPrimitive) value).Value);
+ return Convert.ToUInt64(((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator ushort (JsonValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
- return Convert.ToUInt16 (((JsonPrimitive) value).Value);
+ return Convert.ToUInt16 (((JsonPrimitive) value).Value, NumberFormatInfo.InvariantInfo);
}
public static implicit operator DateTime (JsonValue value)
diff --git a/mcs/class/System.Json/Test/System.Json/JsonValueTest.cs b/mcs/class/System.Json/Test/System.Json/JsonValueTest.cs
index c3347c689d9..02dd106dfd8 100644
--- a/mcs/class/System.Json/Test/System.Json/JsonValueTest.cs
+++ b/mcs/class/System.Json/Test/System.Json/JsonValueTest.cs
@@ -11,6 +11,8 @@ using System;
using System.IO;
using System.Text;
using System.Json;
+using System.Globalization;
+using System.Threading;
namespace MonoTests.System
{
@@ -24,6 +26,14 @@ namespace MonoTests.System
Assert.AreEqual (1, j.Count, "itemcount");
Assert.AreEqual (JsonType.String, j ["a"].JsonType, "type");
Assert.AreEqual ("b", (string) j ["a"], "value");
+
+ JsonValue.Parse ("[{ \"a\": \"b\",}]");
+ }
+
+ [Test]
+ public void LoadWithTrailingComma2 ()
+ {
+ JsonValue.Parse ("[{ \"a\": \"b\",}]");
}
// Test that we correctly serialize JsonArray with null elements.
@@ -41,5 +51,135 @@ namespace MonoTests.System
{
Assert.AreEqual ((new JsonPrimitive ("\"\"")).ToString (), "\"\\\"\\\"\"");
}
+
+ void ExpectError (string s)
+ {
+ try {
+ JsonValue.Parse (s);
+ Assert.Fail ("Expected ArgumentException for `" + s + "'");
+ } catch (ArgumentException) {
+ }
+ }
+
+ // Test whether an exception is thrown for invalid JSON
+ [Test]
+ public void CheckErrors ()
+ {
+ ExpectError (@"-");
+ ExpectError (@"- ");
+ ExpectError (@"1.");
+ ExpectError (@"1. ");
+ ExpectError (@"1e+");
+ ExpectError (@"1 2");
+ ExpectError (@"077");
+
+ ExpectError (@"[1,]");
+
+ //ExpectError (@"{""a"":1,}"); // Not valid JSON, allowed anyway
+ }
+
+ // Parse a json string and compare to the expected value
+ void CheckDouble (double expected, string json)
+ {
+ double jvalue = (double) JsonValue.Parse (json);
+ Assert.AreEqual (expected, jvalue);
+ }
+
+ // Convert a number to json and parse the string, then compare the result to the original value
+ void CheckDouble (double number)
+ {
+ double jvalue = (double) JsonValue.Parse (new JsonPrimitive (number).ToString ());
+ Assert.AreEqual (number, jvalue); // should be exactly the same
+ }
+
+ [Test]
+ public void CheckNumbers ()
+ {
+ CheckDouble (0, "0");
+ CheckDouble (0, "-0");
+ CheckDouble (0, "0.00");
+ CheckDouble (0, "-0.00");
+ CheckDouble (1, "1");
+ CheckDouble (1.1, "1.1");
+ CheckDouble (-1, "-1");
+ CheckDouble (-1.1, "-1.1");
+ CheckDouble (1e-10, "1e-10");
+ CheckDouble (1e+10, "1e+10");
+ CheckDouble (1e-30, "1e-30");
+ CheckDouble (1e+30, "1e+30");
+
+ CheckDouble (1, "\"1\"");
+ CheckDouble (1.1, "\"1.1\"");
+ CheckDouble (-1, "\"-1\"");
+ CheckDouble (-1.1, "\"-1.1\"");
+
+ CheckDouble (double.NaN, "\"NaN\"");
+ CheckDouble (double.PositiveInfinity, "\"Infinity\"");
+ CheckDouble (double.NegativeInfinity, "\"-Infinity\"");
+
+ ExpectError ("NaN");
+ ExpectError ("Infinity");
+ ExpectError ("-Infinity");
+
+ Assert.AreEqual ("1.1", new JsonPrimitive (1.1).ToString ());
+ Assert.AreEqual ("-1.1", new JsonPrimitive (-1.1).ToString ());
+ Assert.AreEqual ("1E-20", new JsonPrimitive (1e-20).ToString ());
+ Assert.AreEqual ("1E+20", new JsonPrimitive (1e+20).ToString ());
+ Assert.AreEqual ("1E-30", new JsonPrimitive (1e-30).ToString ());
+ Assert.AreEqual ("1E+30", new JsonPrimitive (1e+30).ToString ());
+ Assert.AreEqual ("\"NaN\"", new JsonPrimitive (double.NaN).ToString ());
+ Assert.AreEqual ("\"Infinity\"", new JsonPrimitive (double.PositiveInfinity).ToString ());
+ Assert.AreEqual ("\"-Infinity\"", new JsonPrimitive (double.NegativeInfinity).ToString ());
+
+ Assert.AreEqual ("1E-30", JsonValue.Parse ("1e-30").ToString ());
+ Assert.AreEqual ("1E+30", JsonValue.Parse ("1e+30").ToString ());
+
+ CheckDouble (1);
+ CheckDouble (1.1);
+ CheckDouble (1.25);
+ CheckDouble (-1);
+ CheckDouble (-1.1);
+ CheckDouble (-1.25);
+ CheckDouble (1e-20);
+ CheckDouble (1e+20);
+ CheckDouble (1e-30);
+ CheckDouble (1e+30);
+ CheckDouble (3.1415926535897932384626433);
+ CheckDouble (3.1415926535897932384626433e-20);
+ CheckDouble (3.1415926535897932384626433e+20);
+ CheckDouble (double.NaN);
+ CheckDouble (double.PositiveInfinity);
+ CheckDouble (double.NegativeInfinity);
+ CheckDouble (double.MinValue);
+ CheckDouble (double.MaxValue);
+
+ // A number which needs 17 digits (see http://stackoverflow.com/questions/6118231/why-do-i-need-17-significant-digits-and-not-16-to-represent-a-double)
+ CheckDouble (18014398509481982.0);
+
+ // Values around the smallest positive decimal value
+ CheckDouble (1.123456789e-29);
+ CheckDouble (1.123456789e-28);
+
+ CheckDouble (1.1E-29, "0.000000000000000000000000000011");
+ // This is being parsed as a decimal and rounded to 1e-28, even though it can be more accurately be represented by a double
+ //CheckDouble (1.1E-28, "0.00000000000000000000000000011");
+ }
+
+ // Retry the test with different locales
+ [Test]
+ public void CheckNumbersCulture ()
+ {
+ CultureInfo old = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo ("en");
+ CheckNumbers ();
+ Thread.CurrentThread.CurrentCulture = new CultureInfo ("fr");
+ CheckNumbers ();
+ Thread.CurrentThread.CurrentCulture = new CultureInfo ("de");
+ CheckNumbers ();
+ } finally {
+ Thread.CurrentThread.CurrentCulture = old;
+ }
+ }
}
}
diff --git a/mcs/class/System.ServiceModel.Web/System.Runtime.Serialization.Json/JavaScriptReader.cs b/mcs/class/System.ServiceModel.Web/System.Runtime.Serialization.Json/JavaScriptReader.cs
index 891be5db3ee..2d11b1727ae 100644
--- a/mcs/class/System.ServiceModel.Web/System.Runtime.Serialization.Json/JavaScriptReader.cs
+++ b/mcs/class/System.ServiceModel.Web/System.Runtime.Serialization.Json/JavaScriptReader.cs
@@ -26,7 +26,7 @@ namespace System.Runtime.Serialization.Json
{
object v = ReadCore ();
SkipSpaces ();
- if (r.Read () >= 0)
+ if (ReadChar () >= 0)
throw JsonError (String.Format ("extra characters in JSON input"));
return v;
}
@@ -68,8 +68,10 @@ namespace System.Runtime.Serialization.Json
}
while (true) {
SkipSpaces ();
- if (PeekChar () == '}')
+ if (PeekChar () == '}') {
+ ReadChar ();
break;
+ }
string name = ReadStringLiteral ();
SkipSpaces ();
Expect (':');
@@ -160,98 +162,89 @@ namespace System.Runtime.Serialization.Json
// It could return either int, long or decimal, depending on the parsed value.
object ReadNumericLiteral ()
{
+ var sb = new StringBuilder ();
+
bool negative = false;
if (PeekChar () == '-') {
negative = true;
- ReadChar ();
- if (PeekChar () < 0)
- throw JsonError ("Invalid JSON numeric literal; extra negation");
+ sb.Append ((char) ReadChar ());
}
int c;
- decimal val = 0;
int x = 0;
bool zeroStart = PeekChar () == '0';
for (; ; x++) {
c = PeekChar ();
if (c < '0' || '9' < c)
break;
- val = val * 10 + (c - '0');
- ReadChar ();
- if (zeroStart && x == 1 && c == '0')
- throw JsonError ("leading multiple zeros are not allowed");
+ sb.Append ((char) ReadChar ());
+ if (zeroStart && x == 1)
+ throw JsonError ("leading zeros are not allowed");
}
+ if (x == 0) // Reached e.g. for "- "
+ throw JsonError ("Invalid JSON numeric literal; no digit found");
// fraction
-
bool hasFrac = false;
- decimal frac = 0;
int fdigits = 0;
if (PeekChar () == '.') {
hasFrac = true;
- ReadChar ();
+ sb.Append ((char) ReadChar ());
if (PeekChar () < 0)
throw JsonError ("Invalid JSON numeric literal; extra dot");
- decimal d = 10;
while (true) {
c = PeekChar ();
if (c < '0' || '9' < c)
break;
- ReadChar ();
- frac += (c - '0') / d;
- d *= 10;
+ sb.Append ((char) ReadChar ());
fdigits++;
}
if (fdigits == 0)
throw JsonError ("Invalid JSON numeric literal; extra dot");
}
- frac = Decimal.Round (frac, fdigits);
c = PeekChar ();
if (c != 'e' && c != 'E') {
if (!hasFrac) {
- if (negative && int.MinValue <= -val ||
- !negative && val <= int.MaxValue)
- return (int) (negative ? -val : val);
- if (negative && long.MinValue <= -val ||
- !negative && val <= long.MaxValue)
- return (long) (negative ? -val : val);
+ int valueInt;
+ if (int.TryParse (sb.ToString (), NumberStyles.Float, CultureInfo.InvariantCulture, out valueInt))
+ return valueInt;
+
+ long valueLong;
+ if (long.TryParse (sb.ToString (), NumberStyles.Float, CultureInfo.InvariantCulture, out valueLong))
+ return valueLong;
+
+ ulong valueUlong;
+ if (ulong.TryParse (sb.ToString (), NumberStyles.Float, CultureInfo.InvariantCulture, out valueUlong))
+ return valueUlong;
}
- var v = val + frac;
- return negative ? -v : v;
- }
-
- // exponent
-
- ReadChar ();
-
- int exp = 0;
- if (PeekChar () < 0)
- throw new ArgumentException ("Invalid JSON numeric literal; incomplete exponent");
+ decimal valueDecimal;
+ if (decimal.TryParse (sb.ToString (), NumberStyles.Float, CultureInfo.InvariantCulture, out valueDecimal) && valueDecimal != 0)
+ return valueDecimal;
+ } else {
+ // exponent
+ sb.Append ((char) ReadChar ());
+ if (PeekChar () < 0)
+ throw new ArgumentException ("Invalid JSON numeric literal; incomplete exponent");
- bool negexp = false;
- c = PeekChar ();
- if (c == '-') {
- ReadChar ();
- negexp = true;
- }
- else if (c == '+')
- ReadChar ();
-
- if (PeekChar () < 0)
- throw JsonError ("Invalid JSON numeric literal; incomplete exponent");
- while (true) {
c = PeekChar ();
- if (c < '0' || '9' < c)
- break;
- exp = exp * 10 + (c - '0');
- ReadChar ();
+ if (c == '-') {
+ sb.Append ((char) ReadChar ());
+ }
+ else if (c == '+')
+ sb.Append ((char) ReadChar ());
+
+ if (PeekChar () < 0)
+ throw JsonError ("Invalid JSON numeric literal; incomplete exponent");
+ while (true) {
+ c = PeekChar ();
+ if (c < '0' || '9' < c)
+ break;
+ sb.Append ((char) ReadChar ());
+ }
}
- // it is messy to handle exponent, so I just use Decimal.Parse() with assured JSON format.
- if (negexp)
- return new Decimal ((double) (val + frac) / Math.Pow (10, exp));
- int [] bits = Decimal.GetBits (val + frac);
- return new Decimal (bits [0], bits [1], bits [2], negative, (byte) exp);
+
+ return double.Parse (sb.ToString (), NumberStyles.Float, CultureInfo.InvariantCulture);
}
StringBuilder vb = new StringBuilder ();