rd-net/Lifetimes/Serialization/UnsafeReader.cs (415 lines of code) (raw):

using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using JetBrains.Annotations; using JetBrains.Diagnostics; using JetBrains.Util; using JetBrains.Util.Internal; // ReSharper disable BuiltInTypeReferenceStyle // ReSharper disable ArrangeRedundantParentheses namespace JetBrains.Serialization { /// <summary> /// Deserialize data from byte buffer that was initially serialized by <see cref="UnsafeWriter"/> /// <seealso cref="UnsafeReader"/> /// /// </summary> //Can't be struct because internal state must change during methods invocation [PublicAPI] public unsafe class UnsafeReader { private byte* myPtr; private byte* myInitialPtr; private int myMaxlen; public static UnsafeReader CreateReader(byte* ptr, int len) { var reader = new UnsafeReader(); reader.Reset(ptr, len); return reader; } public static void With(byte[] data, Action<UnsafeReader> action) { fixed (byte* resptr = data) { action(CreateReader(resptr, data.Length)); } } //allows to reuse this instance (and get rid of boxing) public UnsafeReader Reset(byte* ptr, int len) { myInitialPtr = ptr; myPtr = ptr; myMaxlen = len; return this; } public int Position => (int) (myPtr - myInitialPtr); public void Skip(int bytes) { AssertLength(bytes); myPtr += bytes; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] private void AssertLength(int size) { // NOTE: having this makes it possible for JIT to just drop the remaining method's body when assertions are disabled if (!Mode.IsAssertion) return; var alreadyRead = (int)(myPtr - myInitialPtr); if (alreadyRead + size > myMaxlen) { ThrowOutOfRange(size, alreadyRead); } } private void ThrowOutOfRange(int size, int alreadyRead) { Assertion.Fail( "Can't read from unsafe reader: alreadyRead={0} size={1} maxlen={2}. " + "Usually this happens when you change serialization format and forget to clear previous entries from disk. " + "For example, you forgot to advance persistent caches version - do this in 'SolutionCaches' and 'ShellCaches' classes).", alreadyRead, size, myMaxlen); } #region Primitive readers [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public byte* ReadRaw(int count) { AssertLength(count); var res = myPtr; myPtr += count; return res; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public bool ReadBoolean() { AssertLength(sizeof(byte)); return *(myPtr++) != 0; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public byte ReadByte() { AssertLength(sizeof(byte)); return *(myPtr++); } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public sbyte ReadSByte() { AssertLength(sizeof(sbyte)); return (sbyte) *(myPtr++); } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public Guid ReadGuid() { var array = ReadArray(reader => reader.ReadByte()); return new Guid(array!); } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public char ReadChar() { AssertLength(sizeof(char)); var x = (char*)myPtr; myPtr = (byte*)(x + 1); return *x; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public decimal ReadDecimal() { AssertLength(sizeof(decimal)); var x = (decimal*)myPtr; myPtr = (byte*)(x + 1); return *x; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public double ReadDouble() { AssertLength(sizeof(double)); var x = (double*)myPtr; myPtr = (byte*)(x + 1); if (!RuntimeInfo.IsUnalignedAccessAllowed) { double d = 0; Buffer.MemoryCopy(x, &d, sizeof(double), sizeof(double)); return d; } else { return *x; } } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public float ReadFloat() { AssertLength(sizeof(float)); var x = (float*)myPtr; myPtr = (byte*)(x + 1); return *x; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public Int16 ReadInt16() { AssertLength(sizeof(Int16)); var x = (Int16*)myPtr; myPtr = (byte*)(x + 1); return *x; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public Int32 ReadInt32() { AssertLength(sizeof(Int32)); var x = (Int32*)myPtr; myPtr = (byte*)(x + 1); return *x; } public int Read7BitEncodedInt32() { // read out an Int32 7 bits at a time var count = 0; var shift = 0; byte b; do { if (shift == 5 * 7) throw new FormatException(); b = ReadByte(); count |= (b & 0b1111111) << shift; shift += 7; } while ((b & 0b10000000) != 0); return count; } public int ReadOftenSmallPositiveInt32() { var byteValue = ReadByte(); if (byteValue == byte.MaxValue) { return ReadInt32(); } return byteValue; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public Int64 ReadInt64() { AssertLength(sizeof(Int64)); var x = (Int64*)myPtr; myPtr = (byte*)(x + 1); return *x; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public UInt16 ReadUInt16() { AssertLength(sizeof(UInt16)); var x = (UInt16*)myPtr; myPtr = (byte*)(x + 1); return *x; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public UInt32 ReadUInt32() { AssertLength(sizeof(UInt32)); var x = (UInt32*)myPtr; myPtr = (byte*)(x + 1); return *x; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public UInt64 ReadUInt64() { AssertLength(sizeof(UInt64)); var x = (UInt64*)myPtr; myPtr = (byte*)(x + 1); return *x; } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public DateTime ReadDateTime() { return new DateTime(ReadLong(), DateTimeKind.Utc); } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public TimeSpan ReadTimeSpan() { return TimeSpan.FromTicks(ReadLong()); } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public Uri ReadUri() { var uri = ReadString(); return new Uri(Uri.UnescapeDataString(uri!), UriKind.RelativeOrAbsolute); } [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public string? ReadString() { var len = ReadInt32(); if (len < 0) return null; if (len == 0) return string.Empty; var raw = (char*) ReadRaw(len * sizeof(char)); if (raw == (char*)0) throw new InvalidOperationException($"Bad memory (null) after reading string of size {len:N}"); var res = new string(raw, 0, len); return res; } public string? ReadStringUTF8() { switch (ReadByte()) { case 0: return null; case 1: return ""; case var byteData: { var bytesCount = byteData == 0xFF ? ReadInt32() : byteData - 1; var startPtr = ReadRaw(bytesCount); var value = Encoding.UTF8.GetString(startPtr, bytesCount); return value; } } } #endregion #region Intern public interface IRawStringIntern { string Intern(RawString raw); } [StructLayout(LayoutKind.Sequential)] public struct RawString { public int Length; public char* Data; public string GetString() { return new string(Data, 0, Length); } } public string? ReadStringInterned(IRawStringIntern intern) { var len = ReadInt32(); if (len < 0) return null; if (len == 0) return string.Empty; var raw = (char*) ReadRaw(len * sizeof(char)); if (raw == (char*)0) throw new InvalidOperationException($"Bad memory (null) after reading string of size {len:N}"); var res = intern.Intern(new RawString {Length = len, Data = raw}); return res; } #endregion #region Delegates public delegate T ReadDelegate<out T>(UnsafeReader reader); public static readonly ReadDelegate<bool> BoolDelegate = reader => reader.ReadBoolean(); public static readonly ReadDelegate<bool> BooleanDelegate = reader => reader.ReadBoolean(); //alias public static readonly ReadDelegate<byte> ByteDelegate = reader => reader.ReadByte(); public static readonly ReadDelegate<Guid> GuidDelegate = reader => reader.ReadGuid(); public static readonly ReadDelegate<char> CharDelegate = reader => reader.ReadChar(); public static readonly ReadDelegate<decimal> DecimalDelegate = reader => reader.ReadDecimal(); public static readonly ReadDelegate<double> DoubleDelegate = reader => reader.ReadDouble(); public static readonly ReadDelegate<float> FloatDelegate = reader => reader.ReadFloat(); public static readonly ReadDelegate<Int16> Int16Delegate = reader => reader.ReadInt16(); public static readonly ReadDelegate<short> ShortDelegate = reader => reader.ReadInt16(); //alias public static readonly ReadDelegate<Int32> Int32Delegate = reader => reader.ReadInt32(); public static readonly ReadDelegate<int> IntDelegate = reader => reader.ReadInt32(); //alias public static readonly ReadDelegate<Int64> Int64Delegate = reader => reader.ReadInt64(); public static readonly ReadDelegate<long> LongDelegate = reader => reader.ReadInt64(); //alias public static readonly ReadDelegate<UInt16> UInt16Delegate = reader => reader.ReadUInt16(); public static readonly ReadDelegate<UInt32> UInt32Delegate = reader => reader.ReadUInt32(); public static readonly ReadDelegate<UInt64> UInt64Delegate = reader => reader.ReadUInt64(); public static readonly ReadDelegate<DateTime> DateTimeDelegate = reader => reader.ReadDateTime(); public static readonly ReadDelegate<Uri> UriDelegate = reader => reader.ReadUri(); public static readonly ReadDelegate<string?> StringDelegate = reader => reader.ReadString(); public static readonly ReadDelegate<byte[]?> ByteArrayDelegate = reader => reader.ReadByteArray(); public static readonly ReadDelegate<bool[]?> BoolArrayDelegate = reader => reader.ReadArray(BooleanDelegate); public static readonly ReadDelegate<int[]?> IntArrayDelegate = reader => reader.ReadIntArray(); public static readonly ReadDelegate<string?[]?> StringArrayDelegate = reader => reader.ReadArray(StringDelegate); #endregion #region Collection readers public T?[]? ReadArray<T>(ReadDelegate<T?> readDelegate) { var len = ReadInt32(); if (len < 0) return null; if (len == 0) return EmptyArray<T>.Instance; var res = new T?[len]; for (var index = 0; index < len; index++) { res[index] = readDelegate(this); } return res; } public int[]? ReadIntArray() { var len = ReadInt32(); if (len < 0) return null; if (len == 0) return EmptyArray<int>.Instance; var res = new int[len]; fixed (int* mem = res) { var size = len * sizeof(int); Memory.CopyMemory(ReadRaw(size), (byte*)mem, size); } return res; } public byte[]? ReadByteArray() { var len = ReadInt32(); if (len < 0) return null; if (len == 0) return EmptyArray<byte>.Instance; var res = new byte[len]; fixed (byte* mem = res) { Memory.CopyMemory(ReadRaw(len), mem, len); } return res; } /// <summary> /// Non optimal collection serialization. One can serialize internal structure (eg. array) instead. /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="TCollection"></typeparam> /// <param name="readDelegate"></param> /// <param name="constructor"></param> /// <returns></returns> public TCollection? ReadCollection<T, TCollection>(ReadDelegate<T> readDelegate, Func<int, TCollection> constructor) where TCollection : ICollection<T> { var count = ReadInt32(); if (count < 0) return default; var col = constructor(count); for (var index = 0; index < count; index++) { col.Add(readDelegate(this)); } return col; } public TDictionary? ReadDictionary<TKey, TValue, TDictionary>( ReadDelegate<TKey> readKeyDelegate, ReadDelegate<TValue> readValueDelegate, Func<int, TDictionary> constructor) where TDictionary : IDictionary<TKey, TValue> { var count = ReadInt32(); if (count < 0) return default; var dict = constructor(count); for (var index = 0; index < count; index++) { dict[readKeyDelegate(this)] = readValueDelegate(this); } return dict; } #endregion [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public bool ReadNullness() => ReadBoolean(); [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public bool ReadBool() => ReadBoolean(); [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public Int16 ReadShort() => ReadInt16(); [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public int ReadInt() => ReadInt32(); [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public long ReadLong() => ReadInt64(); [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public byte ReadUByte() => ReadByte(); [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public ushort ReadUShort() => ReadUInt16(); [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public uint ReadUInt() => ReadUInt32(); [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)] public ulong ReadULong() => ReadUInt64(); public static UInt16 ReadUInt16FromBytes(byte[] bytes) { fixed (byte* bb = bytes) { return *(UInt16*) bb; } } public static Int32 ReadInt32FromBytes(byte[] bytes, int offset = 0) { fixed (byte* bb = bytes) { return *(Int32*) (bb + offset); } } public static Int64 ReadInt64FromBytes(byte[] bytes, int offset = 0) { fixed (byte* bb = bytes) { return *(Int64*) (bb + offset); } } public static UInt64 ReadUInt64FromBytes(byte[] bytes) { fixed (byte* bb = bytes) { return *(UInt64*) bb; } } } }