diff --git a/plist-cil.test/NSNumberTests.cs b/plist-cil.test/NSNumberTests.cs index c4cbc52..9075a3f 100644 --- a/plist-cil.test/NSNumberTests.cs +++ b/plist-cil.test/NSNumberTests.cs @@ -72,6 +72,24 @@ namespace plistcil.test Assert.Equal(doubleValue, number.ToDouble(), 5); } + [Fact] + public void SpanConstructorInvalidValuesTest() + { + Assert.Throws(() => new NSNumber((Span)null, NSNumber.INTEGER)); + Assert.Throws(() => new NSNumber((Span)null, NSNumber.REAL)); + Assert.Throws(() => new NSNumber((Span)Array.Empty(), NSNumber.INTEGER)); + Assert.Throws(() => new NSNumber((Span)Array.Empty(), NSNumber.REAL)); + Assert.Throws(() => new NSNumber((Span)Array.Empty(), 9)); + } + + [Fact] + public void StringAndTypeConstructorInvalidValuesTest() + { + Assert.Throws(() => new NSNumber((string)null, NSNumber.INTEGER)); + Assert.Throws(() => new NSNumber((string)null, NSNumber.REAL)); + Assert.Throws(() => new NSNumber("0", 9)); + } + [Fact] public static void NSNumberConstructorTest() { @@ -138,7 +156,149 @@ namespace plistcil.test Assert.True(number.isReal()); Assert.Equal(7200d, number.ToDouble()); } - #endif +#endif + + public static IEnumerable StringConstructorTestData() + { + return new List + { + // Long values, formatted as hexadecimal values + new object[] { "0x00", false, 0, 0.0 }, + new object[] { "0x1000", true, 0x1000, 1.0 * 0x1000 }, + new object[] { "0x00001000", true, 0x1000, 1.0 * 0x1000 }, + new object[] { "0x0000000000001000", true, 0x1000, 1.0 * 0x1000 }, + + // Long values, formatted as decimal values + new object[] { "0", false, 0, 0.0 }, + new object[] { "10", true, 10, 10.0 }, + + // Decimal values + new object[] { "0.0", false, 0, 0.0 }, + new object[] { "0.10", false, 0, 0.1 }, + new object[] { "3.14", true, 3, 3.14 }, + + // Boolean values + new object[] { "yes", true, 1, 1}, + new object[] { "true", true, 1, 1}, + new object[] { "Yes", true, 1, 1}, + new object[] { "True", true, 1, 1}, + new object[] { "YES", true, 1, 1}, + new object[] { "TRUE", true, 1, 1}, + + new object[] { "no", false, 0, 0}, + new object[] { "false", false, 0, 0}, + new object[] { "No", false, 0, 0}, + new object[] { "False", false, 0, 0}, + new object[] { "NO", false, 0, 0}, + new object[] { "FALSE", false, 0, 0}, + }; + } + + [Theory] + [MemberData(nameof(StringConstructorTestData))] + public void StringConstructorTest(string value, bool boolValue, long longValue, double doubleValue) + { + NSNumber number = new NSNumber(value); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } + + [Fact] + public void StringConstructorInvalidValuesTest() + { + Assert.Throws(() => new NSNumber(null)); + Assert.Throws(() => new NSNumber("plist")); + } + + public static IEnumerable Int32ConstructorTestData() + { + return new List + { + // Long values, formatted as hexadecimal values + new object[] { 0, false, 0, 0.0 }, + new object[] { 1, true, 1, 1.0 }, + new object[] { -1, true, -1, -1.0 }, + new object[] { int.MaxValue, true, int.MaxValue, int.MaxValue }, + new object[] { int.MinValue, true, int.MinValue, int.MinValue }, + }; + } + + [Theory] + [MemberData(nameof(Int32ConstructorTestData))] + public void Int32ConstructorTest(int value, bool boolValue, long longValue, double doubleValue) + { + NSNumber number = new NSNumber(value); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } + + public static IEnumerable Int64ConstructorTestData() + { + return new List + { + // Long values, formatted as hexadecimal values + new object[] { 0, false, 0, 0.0 }, + new object[] { 1, true, 1, 1.0 }, + new object[] { -1, true, -1, -1.0 }, + new object[] { long.MaxValue, true, long.MaxValue, long.MaxValue }, + new object[] { long.MinValue, true, long.MinValue, long.MinValue }, + }; + } + + [Theory] + [MemberData(nameof(Int64ConstructorTestData))] + public void Int64ConstructorTest(long value, bool boolValue, long longValue, double doubleValue) + { + NSNumber number = new NSNumber(value); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } + + public static IEnumerable DoubleConstructorTestData() + { + return new List + { + // Long values, formatted as hexadecimal values + new object[] { 0.0, false, 0, 0.0 }, + new object[] { 1.0, true, 1, 1.0 }, + new object[] { -1.0, true, -1, -1.0 }, + new object[] { double.Epsilon, false, 0, double.Epsilon }, + new object[] { double.MaxValue, true, long.MinValue /* Overflow! */, double.MaxValue }, + new object[] { double.MinValue, true, long.MinValue, double.MinValue }, + }; + } + + [Theory] + [MemberData(nameof(DoubleConstructorTestData))] + public void DoubleConstructorTest(double value, bool boolValue, long longValue, double doubleValue) + { + NSNumber number = new NSNumber(value); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } + public static IEnumerable BoolConstructorTestData() + { + return new List + { + // Long values, formatted as hexadecimal values + new object[] { false, false, 0, 0.0 }, + new object[] { true, true, 1, 1.0 }, + }; + } + + [Theory] + [MemberData(nameof(BoolConstructorTestData))] + public void BoolConstructorTest(bool value, bool boolValue, long longValue, double doubleValue) + { + NSNumber number = new NSNumber(value); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } [Fact] public void EqualTest() diff --git a/plist-cil/BinaryPropertyListParser.cs b/plist-cil/BinaryPropertyListParser.cs index 88a29d4..d53d536 100644 --- a/plist-cil/BinaryPropertyListParser.cs +++ b/plist-cil/BinaryPropertyListParser.cs @@ -515,6 +515,16 @@ namespace Claunia.PropertyList /// The bytes representing the long integer. public static long ParseLong(ReadOnlySpan bytes) { + if(bytes == null) + { + throw new ArgumentNullException(nameof(bytes)); + } + + if(bytes.Length == 0) + { + throw new ArgumentOutOfRangeException(nameof(bytes)); + } + // https://opensource.apple.com/source/CF/CF-1153.18/CFBinaryPList.c, // __CFBinaryPlistCreateObjectFiltered, case kCFBinaryPlistMarkerInt: // @@ -564,6 +574,11 @@ namespace Claunia.PropertyList /// The bytes representing the double. public static double ParseDouble(ReadOnlySpan bytes) { + if(bytes == null) + { + throw new ArgumentNullException(nameof(bytes)); + } + if(bytes.Length == 8) return BitConverter.Int64BitsToDouble(ParseLong(bytes)); if(bytes.Length == 4) return BitConverter.ToSingle(BitConverter.GetBytes(ParseLong(bytes)), 0); diff --git a/plist-cil/NSNumber.cs b/plist-cil/NSNumber.cs index ed91a94..272d3d1 100644 --- a/plist-cil/NSNumber.cs +++ b/plist-cil/NSNumber.cs @@ -83,7 +83,7 @@ namespace Claunia.PropertyList longValue = (long)Math.Round(doubleValue); break; - default: throw new ArgumentException("Type argument is not valid."); + default: throw new ArgumentException("Type argument is not valid.", nameof(type)); } this.type = type; @@ -124,13 +124,12 @@ namespace Claunia.PropertyList long l; double d; - if(text.StartsWith("0x") && long.TryParse("", NumberStyles.HexNumber, CultureInfo.InvariantCulture, out l)) + if(text.StartsWith("0x") && long.TryParse(text.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out l)) { doubleValue = longValue = l; type = INTEGER; } - - if(long.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out l)) + else if(long.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out l)) { doubleValue = longValue = l; type = INTEGER; @@ -151,6 +150,7 @@ namespace Claunia.PropertyList if(isTrue || isFalse) { type = BOOLEAN; + boolValue = isTrue; doubleValue = longValue = boolValue ? 1 : 0; } else