Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move away from BinaryFormatter for ActiveX purposes #9104

Merged
merged 1 commit into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@ public static explicit operator VARIANT(IUnknown* unknown)
private static T ThrowInvalidCast<T>() => throw new InvalidCastException();

/// <summary>
/// Converts the given object to <see cref="VARIANT"/>. WARNING: Only handles <see cref="string"/>.
/// Converts the given object to <see cref="VARIANT"/>.
/// </summary>
public static VARIANT FromObject(object? value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal question for my better personal understanding:
Is this for the VARIANT type that has been used in the context of VB6?
If yes, there once was a DBNull subtype also, if I (vaguely) remember correctly (and I can be completely wrong). I am just mentioning it, and maybe it needs to be taken into consideration?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is a special null type. It's "handled" for the moment in that we fall back to the .NET interop conversion here. What we have to do is get the rest of this implemented on our side for AOT purposes.

{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;

Expand All @@ -13,109 +12,6 @@ namespace System.Windows.Forms.BinaryFormat;
/// </summary>
internal static class BinaryFormatReader
{
/// <summary>
/// Reads a binary formatted string.
/// </summary>
/// <exception cref="SerializationException">Data isn't a valid string.</exception>
public static string ReadString(Stream stream)
{
BinaryFormattedObject format = new(stream, leaveOpen: true);
if (format.RecordCount < 3 || format[1] is not BinaryObjectString value)
{
throw new SerializationException();
}

return value.Value;
}

/// <summary>
/// Reads a binary formatted primitive list.
/// </summary>
/// <exception cref="NotSupportedException"><typeparamref name="T"/> was not primitive.</exception>
/// <exception cref="SerializationException">Data isn't a valid <see cref="List{T}"/>.</exception>
public static List<T> ReadPrimitiveList<T>(Stream stream)
where T : unmanaged
{
if (!typeof(T).IsPrimitive)
{
throw new NotSupportedException($"{nameof(T)} is not primitive.");
}

BinaryFormattedObject format = new(stream, leaveOpen: true);
if (format.RecordCount < 4
|| format[1] is not SystemClassWithMembersAndTypes classInfo
|| !classInfo.Name.StartsWith($"System.Collections.Generic.List`1[[{typeof(T).FullName}")
|| format[2] is not ArraySinglePrimitive array
|| array.PrimitiveType != Record.GetPrimitiveType(typeof(T)))
{
throw new SerializationException();
}

int count;
try
{
count = (int)classInfo["_size"];
}
catch (Exception ex)
{
throw ex.ConvertToSerializationException();
}

List<T> list = new(count);
list.AddRange(array.Take(count).Cast<T>());
return list;
}

/// <summary>
/// Reads a binary formatted <see cref="Hashtable"/>. Only accepts <see langword="string"/> keys
/// and <see langword="string"/>? values.
/// </summary>
/// <exception cref="SerializationException">
/// Data isn't a valid <see langword="string"/> - <see langword="string"/>? <see cref="Hashtable"/>.
/// </exception>
public static Hashtable ReadHashtableOfStrings(Stream stream)
{
BinaryFormattedObject format = new(stream, leaveOpen: true);
if (format.RecordCount < 5
|| format[1] is not SystemClassWithMembersAndTypes classInfo
|| classInfo.Name != "System.Collections.Hashtable"
|| format[2] is not ArraySingleObject keys
|| format[3] is not ArraySingleObject values
|| keys.Length != values.Length)
{
throw new SerializationException();
}

Hashtable hashtable = new(keys.Length);
for (int i = 0; i < keys.Length; i++)
{
string key = keys[i] switch
{
BinaryObjectString keyString => keyString.Value,
MemberReference reference => format[reference.IdRef] switch
{
BinaryObjectString refString => refString.Value,
_ => throw new SerializationException()
},
_ => throw new SerializationException()
};

hashtable[key] = values[i] switch
{
BinaryObjectString valueString => valueString.Value,
ObjectNull => null,
MemberReference reference => format[reference.IdRef] switch
{
BinaryObjectString refString => refString.Value,
_ => throw new SerializationException()
},
_ => throw new SerializationException(),
};
}

return hashtable;
}

/// <summary>
/// Creates a <see cref="DateTime"/> object from raw data with validation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,15 @@ public static void WritePrimitiveList<T>(Stream stream, IReadOnlyList<T> list)
}

/// <summary>
/// Writes a <see cref="Hashtable"/> of strings to the given stream in binary format.
/// Writes a <see cref="Hashtable"/> of primitive to primitive values to the given stream in binary format.
/// </summary>
/// <exception cref="ArgumentException"><paramref name="hashtable"/> contained non-<see langword="string"/> values</exception>
public static void WriteHashtableOfStrings(Stream stream, Hashtable hashtable)
/// <remarks>
/// <para>
/// Primitive types are anything in the <see cref="PrimitiveType"/> enum.
/// </para>
/// </remarks>
/// <exception cref="ArgumentException"><paramref name="hashtable"/> contained non-primitive values.</exception>
public static void WritePrimitiveHashtable(Stream stream, Hashtable hashtable)
{
// Get the ISerializable data from the hashtable. This way we don't have to worry about
// getting the LoadFactor, Version, etc. wrong.
Expand Down Expand Up @@ -355,7 +360,7 @@ public static void WriteHashtableOfStrings(Stream stream, Hashtable hashtable)

systemClass.Write(writer);

// We've used 1, 2 and 3 for the class id and array ids
// We've used 1, 2 and 3 for the class id and array ids.
int currentId = 4;

// Using an array converter to save a temporary array allocation. By not doing this we come much closer
Expand All @@ -368,7 +373,7 @@ public static void WriteHashtableOfStrings(Stream stream, Hashtable hashtable)
(object value) => value switch
{
string stringValue => strings.GetStringRecord(stringValue, ref currentId),
_ => throw new ArgumentException("Only strings are permitted")
_ => new MemberPrimitiveTyped(value)
});

ArraySingleObject array = new(new(2, keys.Length), keyConverter);
Expand All @@ -380,46 +385,12 @@ public static void WriteHashtableOfStrings(Stream stream, Hashtable hashtable)
{
null => ObjectNull.Instance,
string stringValue => strings.GetStringRecord(stringValue, ref currentId),
_ => throw new ArgumentException("Only strings are permitted")
_ => new MemberPrimitiveTyped(value)
});

array = new(new(3, values.Length), valueConverter);
array.Write(writer);

MessageEnd.Instance.Write(writer);
}

public static PrimitiveType GetPrimitiveType(object @object)
{
if (@object is null)
{
return PrimitiveType.Null;
}

Type type = @object.GetType();
if (type == typeof(TimeSpan))
{
return PrimitiveType.TimeSpan;
}

return Type.GetTypeCode(type) switch
{
TypeCode.Boolean => PrimitiveType.Boolean,
TypeCode.Char => PrimitiveType.Char,
TypeCode.SByte => PrimitiveType.SByte,
TypeCode.Byte => PrimitiveType.Byte,
TypeCode.Int16 => PrimitiveType.Int16,
TypeCode.UInt16 => PrimitiveType.UInt16,
TypeCode.Int32 => PrimitiveType.Int32,
TypeCode.UInt32 => PrimitiveType.UInt32,
TypeCode.Int64 => PrimitiveType.Int64,
TypeCode.UInt64 => PrimitiveType.UInt64,
TypeCode.Single => PrimitiveType.Single,
TypeCode.Double => PrimitiveType.Double,
TypeCode.Decimal => PrimitiveType.Decimal,
TypeCode.DateTime => PrimitiveType.DateTime,
TypeCode.String => PrimitiveType.String,
_ => default,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,6 @@ namespace System.Windows.Forms.BinaryFormat;
/// </remarks>
internal sealed class BinaryFormattedObject
{
private static readonly string[] s_systemPrimitiveTypeNames = new string[]
{
typeof(int).FullName!,
typeof(long).FullName!,
typeof(bool).FullName!,
typeof(char).FullName!,
typeof(float).FullName!,
typeof(double).FullName!,
typeof(sbyte).FullName!,
typeof(byte).FullName!,
typeof(short).FullName!,
typeof(ushort).FullName!,
typeof(uint).FullName!,
typeof(ulong).FullName!,
};

// Don't reserve space in collections based on read lengths for more than this size to defend against corrupted lengths.
#if DEBUG
internal const int MaxNewCollectionSize = 1024 * 10;
Expand Down Expand Up @@ -94,92 +78,4 @@ public BinaryFormattedObject(Stream stream, bool leaveOpen = false)
/// can be referenced by other records.
/// </summary>
public IRecord this[Id id] => _recordMap[id];

/// <summary>
/// Trys to get this object as a primitive type or string.
/// </summary>
/// <returns><see langword="true"/> if this represented a primitive type or string.</returns>
public bool TryGetPrimitiveType([NotNullWhen(true)] out object? value)
{
try
{
return TryGetPrimitiveTypeInternal(out value);
}
catch (Exception ex) when (!ClientUtils.IsCriticalException(ex))
{
value = default;
return false;
}
}

private bool TryGetPrimitiveTypeInternal([NotNullWhen(true)] out object? value)
{
value = null;
if (RecordCount != 3)
{
return false;
}

if (_records[1] is BinaryObjectString binaryString)
{
value = binaryString.Value;
return true;
}

if (_records[1] is not SystemClassWithMembersAndTypes systemClass)
{
return false;
}

// Basic primitive types
if (s_systemPrimitiveTypeNames.Contains(systemClass.Name)
&& systemClass.MemberTypeInfo[0].Type == BinaryType.Primitive)
{
value = systemClass.MemberValues[0];
return true;
}

// Handle decimal, nint, nuint, TimeSpan, DateTime

if (systemClass.Name == typeof(TimeSpan).FullName)
{
value = new TimeSpan((long)systemClass.MemberValues[0]);
return true;
}

if (systemClass.Name == typeof(DateTime).FullName)
{
value = BinaryFormatReader.CreateDateTimeFromData((long)systemClass["dateData"]);
return true;
}

if (systemClass.Name == typeof(nint).FullName)
{
// Rehydrating still throws even though casting doesn't any more
value = checked((nint)(long)systemClass.MemberValues[0]);
return true;
}

if (systemClass.Name == typeof(nuint).FullName)
{
value = checked((nuint)(ulong)systemClass.MemberValues[0]);
return true;
}

if (systemClass.Name == typeof(decimal).FullName)
{
Span<int> bits = stackalloc int[4]
{
(int)systemClass["lo"],
(int)systemClass["mid"],
(int)systemClass["hi"],
(int)systemClass["flags"]
};

value = new decimal(bits);
return true;
}

return false;
}
}
Loading