|
3 | 3 |
|
4 | 4 | using System.Globalization; |
5 | 5 | using System.IO; |
| 6 | +using System.Reflection; |
6 | 7 | using System.Reflection.Metadata; |
7 | 8 | using System.Runtime.CompilerServices; |
8 | 9 | using System.Runtime.Serialization; |
| 10 | +using System.Threading; |
9 | 11 |
|
10 | 12 | namespace System.Formats.Nrbf.Utils; |
11 | 13 |
|
12 | 14 | internal static class BinaryReaderExtensions |
13 | 15 | { |
| 16 | + private static object? s_baseAmbiguousDstDateTime; |
| 17 | + |
14 | 18 | internal static BinaryArrayType ReadArrayType(this BinaryReader reader) |
15 | 19 | { |
16 | 20 | byte arrayType = reader.ReadByte(); |
@@ -70,36 +74,70 @@ internal static object ReadPrimitiveValue(this BinaryReader reader, PrimitiveTyp |
70 | 74 | PrimitiveType.Single => reader.ReadSingle(), |
71 | 75 | PrimitiveType.Double => reader.ReadDouble(), |
72 | 76 | PrimitiveType.Decimal => decimal.Parse(reader.ReadString(), CultureInfo.InvariantCulture), |
73 | | - PrimitiveType.DateTime => CreateDateTimeFromData(reader.ReadInt64()), |
| 77 | + PrimitiveType.DateTime => CreateDateTimeFromData(reader.ReadUInt64()), |
74 | 78 | _ => new TimeSpan(reader.ReadInt64()), |
75 | 79 | }; |
76 | 80 |
|
77 | | - // TODO: fix https://github.com/dotnet/runtime/issues/102826 |
78 | 81 | /// <summary> |
79 | 82 | /// Creates a <see cref="DateTime"/> object from raw data with validation. |
80 | 83 | /// </summary> |
81 | | - /// <exception cref="SerializationException"><paramref name="data"/> was invalid.</exception> |
82 | | - internal static DateTime CreateDateTimeFromData(long data) |
| 84 | + /// <exception cref="SerializationException"><paramref name="dateData"/> was invalid.</exception> |
| 85 | + internal static DateTime CreateDateTimeFromData(ulong dateData) |
83 | 86 | { |
84 | | - // Copied from System.Runtime.Serialization.Formatters.Binary.BinaryParser |
85 | | - |
86 | | - // Use DateTime's public constructor to validate the input, but we |
87 | | - // can't return that result as it strips off the kind. To address |
88 | | - // that, store the value directly into a DateTime via an unsafe cast. |
89 | | - // See BinaryFormatterWriter.WriteDateTime for details. |
| 87 | + ulong ticks = dateData & 0x3FFFFFFF_FFFFFFFFUL; |
| 88 | + DateTimeKind kind = (DateTimeKind)(dateData >> 62); |
90 | 89 |
|
91 | 90 | try |
92 | 91 | { |
93 | | - const long TicksMask = 0x3FFFFFFFFFFFFFFF; |
94 | | - _ = new DateTime(data & TicksMask); |
| 92 | + return ((uint)kind <= (uint)DateTimeKind.Local) ? new DateTime((long)ticks, kind) : CreateFromAmbiguousDst(ticks); |
95 | 93 | } |
96 | 94 | catch (ArgumentException ex) |
97 | 95 | { |
98 | | - // Bad data |
99 | 96 | throw new SerializationException(ex.Message, ex); |
100 | 97 | } |
101 | 98 |
|
102 | | - return Unsafe.As<long, DateTime>(ref data); |
| 99 | + [MethodImpl(MethodImplOptions.NoInlining)] |
| 100 | + static DateTime CreateFromAmbiguousDst(ulong ticks) |
| 101 | + { |
| 102 | + // There's no public API to create a DateTime from an ambiguous DST, and we |
| 103 | + // can't use private reflection to access undocumented .NET Framework APIs. |
| 104 | + // However, the ISerializable pattern *is* a documented protocol, so we can |
| 105 | + // use DateTime's serialization ctor to create a zero-tick "ambiguous" instance, |
| 106 | + // then keep reusing it as the base to which we can add our tick offsets. |
| 107 | + |
| 108 | + if (s_baseAmbiguousDstDateTime is not DateTime baseDateTime) |
| 109 | + { |
| 110 | +#pragma warning disable SYSLIB0050 // Type or member is obsolete |
| 111 | + SerializationInfo si = new(typeof(DateTime), new FormatterConverter()); |
| 112 | + // We don't know the value of "ticks", so we don't specify it. |
| 113 | + // If the code somehow runs on a very old runtime that does not know the concept of "dateData" |
| 114 | + // (it should not be possible as the library targets .NET Standard 2.0) |
| 115 | + // the ctor is going to throw rather than silently return an invalid value. |
| 116 | + si.AddValue("dateData", 0xC0000000_00000000UL); // new value (serialized as ulong) |
| 117 | + |
| 118 | +#if NET |
| 119 | + baseDateTime = CallPrivateSerializationConstructor(si, new StreamingContext(StreamingContextStates.All)); |
| 120 | +#else |
| 121 | + ConstructorInfo ci = typeof(DateTime).GetConstructor( |
| 122 | + BindingFlags.Instance | BindingFlags.NonPublic, |
| 123 | + binder: null, |
| 124 | + new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, |
| 125 | + modifiers: null); |
| 126 | + |
| 127 | + baseDateTime = (DateTime)ci.Invoke(new object[] { si, new StreamingContext(StreamingContextStates.All) }); |
| 128 | +#endif |
| 129 | + |
| 130 | +#pragma warning restore SYSLIB0050 // Type or member is obsolete |
| 131 | + Volatile.Write(ref s_baseAmbiguousDstDateTime, baseDateTime); // it's ok if two threads race here |
| 132 | + } |
| 133 | + |
| 134 | + return baseDateTime.AddTicks((long)ticks); |
| 135 | + } |
| 136 | + |
| 137 | +#if NET |
| 138 | + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] |
| 139 | + extern static DateTime CallPrivateSerializationConstructor(SerializationInfo si, StreamingContext ct); |
| 140 | +#endif |
103 | 141 | } |
104 | 142 |
|
105 | 143 | internal static bool? IsDataAvailable(this BinaryReader reader, long requiredBytes) |
|
0 commit comments