diff --git a/src/HotAvalonia/AvaloniaRuntimeXamlScanner.cs b/src/HotAvalonia/AvaloniaRuntimeXamlScanner.cs index 566b2c7..3819e76 100644 --- a/src/HotAvalonia/AvaloniaRuntimeXamlScanner.cs +++ b/src/HotAvalonia/AvaloniaRuntimeXamlScanner.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Reflection.Emit; using HotAvalonia.Helpers; using HotAvalonia.Reflection; @@ -196,9 +195,9 @@ private static bool TryExtractControlUri(MethodInfo populateMethod, [NotNullWhen if (methodBody is null) return false; - int ldstrLocation = methodBody.Length > commonLdstrLocation && methodBody[commonLdstrLocation] == OpCodes.Ldstr.Value + int ldstrLocation = methodBody.Length > commonLdstrLocation && methodBody[commonLdstrLocation] == OpCodeHelper.LdstrValue ? commonLdstrLocation - : MethodBodyReader.IndexOf(methodBody, OpCodes.Ldstr.Value); + : MethodBodyReader.IndexOf(methodBody, OpCodeHelper.LdstrValue); int uriTokenLocation = ldstrLocation + 1; @@ -251,19 +250,19 @@ private static IEnumerable ExtractAvaloniaControls(ReadOnly while (reader.Next()) { short opCode = reader.OpCode.Value; - if (opCode == OpCodes.Ret.Value) + if (opCode is OpCodeHelper.RetValue) { (str, uri) = (null, null); continue; } - if (opCode == OpCodes.Ldstr.Value) + if (opCode is OpCodeHelper.LdstrValue) { str = reader.ResolveString(module); continue; } - if (opCode != OpCodes.Call.Value && opCode != OpCodes.Newobj.Value) + if (opCode is not (OpCodeHelper.CallValue or OpCodeHelper.NewobjValue)) continue; MethodBase method = reader.ResolveMethod(module); diff --git a/src/HotAvalonia/Helpers/OpCodeHelper.cs b/src/HotAvalonia/Helpers/OpCodeHelper.cs new file mode 100644 index 0000000..c729f21 --- /dev/null +++ b/src/HotAvalonia/Helpers/OpCodeHelper.cs @@ -0,0 +1,165 @@ +using System.Reflection; +using System.Reflection.Emit; + +namespace HotAvalonia.Helpers; + +/// +/// Provides helper methods for working with CIL opcodes. +/// +internal static class OpCodeHelper +{ + /// + /// The for the 'call' instruction. + /// + internal const short CallValue = 0x28; + + /// + /// The for the 'ret' instruction. + /// + internal const short RetValue = 0x2A; + + /// + /// The for the 'switch' instruction. + /// + internal const short SwitchValue = 0x45; + + /// + /// The for the 'ldstr' instruction. + /// + internal const short LdstrValue = 0x72; + + /// + /// The for the 'newobj' instruction. + /// + internal const short NewobjValue = 0x73; + + /// + /// The flag indicating that the is represented by 2 bytes. + /// + private const int Int16OpCodeFlag = 0xFE00; + + /// + /// The marker value for two-byte opcodes. + /// + private const int Int16OpCodeMarker = 0xFE; + + /// + /// The instruction set containing all instances. + /// + private static readonly Lazy s_opCodes = new(CreateInstructionSet, isThreadSafe: true); + + /// + /// Attempts to read an from the given IL span. + /// + /// The span containing the IL bytecode. + /// When this method returns, contains the if the method is successful. + /// true if an was successfully read; otherwise, false. + public static bool TryReadOpCode(ReadOnlySpan il, out OpCode opCode) + { + opCode = OpCodes.Nop; + + if (il.IsEmpty) + return false; + + int opCodeValue = il[0]; + if (opCodeValue >= Int16OpCodeMarker) + { + if (il.Length < 1) + return false; + + opCodeValue = (opCodeValue << 8) | il[1]; + } + + return TryGetOpCode(opCodeValue, out opCode); + } + + /// + /// Tries to get the associated with the given value. + /// + /// The opcode value. + /// When this method returns, contains the if the method is successful. + /// true if the was found for the given value; otherwise, false. + public static bool TryGetOpCode(int value, out OpCode opCode) + { + int index = GetOpCodeIndex(value); + OpCode[] opCodes = s_opCodes.Value; + if ((uint)index < (uint)opCodes.Length) + { + opCode = opCodes[index]; + return true; + } + + opCode = OpCodes.Nop; + return false; + } + + /// + /// Creates an instruction set containing all instances. + /// + /// An array of instances. + private static OpCode[] CreateInstructionSet() + { + List opCodes = new(256); + opCodes.AddRange(ExtractAllOpCodes()); + + int maxIndex = opCodes.Max(static x => GetOpCodeIndex(x.Value)); + int instructionSetSize = maxIndex + 1; + OpCode[] instructionSet = new OpCode[instructionSetSize]; + + foreach (OpCode opCode in opCodes) + { + instructionSet[GetOpCodeIndex(opCode.Value)] = opCode; + } + + return instructionSet; + } + + /// + /// Gets the index of the given opcode in the instruction set. + /// + /// The opcode value. + /// The index of the opcode. + private static int GetOpCodeIndex(int value) + => (value & Int16OpCodeFlag) == Int16OpCodeFlag ? (256 + (value & 0xFF)) : (value & 0xFF); + + /// + /// Extracts all opcodes from the class. + /// + /// + /// All opcodes defined in the class. + /// + private static IEnumerable ExtractAllOpCodes() + { + FieldInfo[] fields = typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static); + return fields.Where(static x => x.FieldType == typeof(OpCode)).Select(static x => (OpCode)x.GetValue(null)); + } + + /// + /// Calculates the size of the operand associated with the given opcode. + /// + /// The operation code in question. + /// + /// The size of the operand in bytes; or 0 if the opcode has no operand. + /// + public static int GetOperandSize(this OpCode opCode) + => opCode.Size is 0 ? 0 : GetOperandSize(opCode.OperandType); + + /// + /// Determines the size of an operand based on its type. + /// + /// The type of the operand. + /// + /// The size of the operand in bytes. + /// + public static int GetOperandSize(this OperandType operandType) => operandType switch + { + OperandType.InlineBrTarget or OperandType.InlineField or OperandType.InlineI + or OperandType.InlineMethod or OperandType.InlineSig or OperandType.InlineString + or OperandType.InlineSwitch or OperandType.InlineTok or OperandType.InlineType + or OperandType.ShortInlineR => sizeof(int), + OperandType.InlineI8 or OperandType.InlineR => sizeof(long), + OperandType.InlineVar => sizeof(short), + OperandType.ShortInlineBrTarget or OperandType.ShortInlineI or OperandType.ShortInlineVar => sizeof(byte), + _ => 0, + }; +} diff --git a/src/HotAvalonia/Reflection/MethodBodyReader.cs b/src/HotAvalonia/Reflection/MethodBodyReader.cs index 16b4bdf..b42721c 100644 --- a/src/HotAvalonia/Reflection/MethodBodyReader.cs +++ b/src/HotAvalonia/Reflection/MethodBodyReader.cs @@ -1,6 +1,7 @@ using System.Reflection; using System.Reflection.Emit; using System.Runtime.InteropServices; +using HotAvalonia.Helpers; #if NETSTANDARD2_0 using BitConverter = HotAvalonia.Helpers.BitHelper; @@ -14,17 +15,6 @@ namespace HotAvalonia.Reflection; /// internal struct MethodBodyReader { - /// - /// A flag representing a 16-bit opcode value. - /// - private const short Int16OpCodeFlag = 0xFE; - - /// - /// A dictionary containing mapped to their respective opcode values. - /// - private static readonly Lazy> s_opCodes = new( - static () => GetAllOpCodes().ToDictionary(static x => x.Value)); - /// /// The byte sequence that constitutes the method body. /// @@ -80,7 +70,7 @@ public readonly ReadOnlySpan Operand if (size is 0) return Array.Empty(); - return _methodBody.Slice(_position + size, GetOperandSize(_opCode.OperandType)).Span; + return _methodBody.Slice(_position + size, _opCode.OperandType.GetOperandSize()).Span; } } @@ -91,7 +81,7 @@ public readonly ReadOnlySpan JumpTable { get { - if (_opCode.Value != OpCodes.Switch.Value) + if (_opCode.Value is not OpCodeHelper.SwitchValue) return Array.Empty(); int start = _position + sizeof(byte) + sizeof(int); @@ -112,28 +102,15 @@ public bool Next() { ReadOnlySpan methodBody = _methodBody.Span; int nextPosition = _bytesConsumed; - if (nextPosition >= methodBody.Length) - return false; - - short newOpCodeValue = methodBody[nextPosition]; - int operandStart = nextPosition + 1; - if (newOpCodeValue >= Int16OpCodeFlag) - { - if (operandStart >= methodBody.Length) - return false; - - newOpCodeValue = (short)((newOpCodeValue << 8) | methodBody[operandStart]); - ++operandStart; - } - - if (!s_opCodes.Value.TryGetValue(newOpCodeValue, out OpCode newOpCode)) + if (!OpCodeHelper.TryReadOpCode(methodBody.Slice(nextPosition), out OpCode newOpCode)) return false; - int nextBytesConsumed = operandStart + GetOperandSize(newOpCode.OperandType); + int operandStart = nextPosition + newOpCode.Size; + int nextBytesConsumed = operandStart + newOpCode.OperandType.GetOperandSize(); if (nextBytesConsumed > methodBody.Length) return false; - if (newOpCode.Value == OpCodes.Switch.Value) + if (newOpCode.Value is OpCodeHelper.SwitchValue) { int n = BitConverter.ToInt32(methodBody.Slice(operandStart)); nextBytesConsumed += n * sizeof(int); @@ -157,7 +134,7 @@ public bool Next() /// The zero-based index of the first occurrence of the specified op code in the method body; /// or -1 if the opcode is not found. /// - public static int IndexOf(ReadOnlyMemory methodBody, short opCode) + public static int IndexOf(ReadOnlyMemory methodBody, int opCode) { MethodBodyReader reader = new(methodBody); while (reader.Next()) @@ -373,53 +350,12 @@ public readonly Type ResolveType(Module module, Type[] genericTypeArguments, Typ /// Thrown when the operand size does not match the expected size. private readonly void EnsureOperandSize(int size) { - if (GetOperandSize(_opCode) == size) + if (_opCode.GetOperandSize() == size) return; - ThrowInvalidOperationException_SizeDoesNotMatch(size, GetOperandSize(_opCode)); + ThrowInvalidOperationException_SizeDoesNotMatch(size, _opCode.GetOperandSize()); static void ThrowInvalidOperationException_SizeDoesNotMatch(int expectedSize, int actualSize) => throw new InvalidOperationException($"The operand size ({actualSize} bytes) does not match the expected size ({expectedSize} bytes)."); } - - /// - /// Calculates the size of the operand associated with the given opcode. - /// - /// The operation code in question. - /// - /// The size of the operand in bytes; or 0 if the opcode has no operand. - /// - private static int GetOperandSize(OpCode opCode) - => opCode.Size is 0 ? 0 : GetOperandSize(opCode.OperandType); - - /// - /// Determines the size of an operand based on its type. - /// - /// The type of the operand. - /// - /// The size of the operand in bytes. - /// - public static int GetOperandSize(OperandType operandType) => operandType switch - { - OperandType.InlineBrTarget or OperandType.InlineField or OperandType.InlineI - or OperandType.InlineMethod or OperandType.InlineSig or OperandType.InlineString - or OperandType.InlineSwitch or OperandType.InlineTok or OperandType.InlineType - or OperandType.ShortInlineR => sizeof(int), - OperandType.InlineI8 or OperandType.InlineR => sizeof(long), - OperandType.InlineVar => sizeof(short), - OperandType.ShortInlineBrTarget or OperandType.ShortInlineI or OperandType.ShortInlineVar => sizeof(byte), - _ => 0, - }; - - /// - /// Retrieves all opcodes defined in the class. - /// - /// - /// All opcodes defined in the class. - /// - private static IEnumerable GetAllOpCodes() - { - FieldInfo[] fields = typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static); - return fields.Where(static x => x.FieldType == typeof(OpCode)).Select(static x => (OpCode)x.GetValue(null)); - } }