test: replace render source freezes
This commit is contained in:
parent
c31a9ac411
commit
5a33369074
7 changed files with 425 additions and 345 deletions
109
tests/AcDream.App.Tests/Architecture/CompiledCallGraph.cs
Normal file
109
tests/AcDream.App.Tests/Architecture/CompiledCallGraph.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace AcDream.App.Tests.Architecture;
|
||||
|
||||
internal readonly record struct CompiledCall(int Offset, MethodBase Target);
|
||||
|
||||
/// <summary>
|
||||
/// Reads compiled call/new-object edges from a method body. Architecture tests
|
||||
/// use this when the contract is an ownership or ordering edge that cannot be
|
||||
/// exercised through a public result, avoiding formatting- and comment-sensitive
|
||||
/// source-string assertions.
|
||||
/// </summary>
|
||||
internal static class CompiledCallGraph
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<short, OpCode> OpCodesByValue =
|
||||
typeof(OpCodes)
|
||||
.GetFields(BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(field => field.FieldType == typeof(OpCode))
|
||||
.Select(field => (OpCode)field.GetValue(null)!)
|
||||
.ToDictionary(opCode => opCode.Value);
|
||||
|
||||
public static IReadOnlyList<CompiledCall> Read(MethodBase method)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(method);
|
||||
byte[] il = method.GetMethodBody()?.GetILAsByteArray()
|
||||
?? throw new InvalidOperationException(
|
||||
$"{method.DeclaringType?.FullName}.{method.Name} has no compiled body.");
|
||||
Type[]? declaringArguments = method.DeclaringType?.IsGenericType == true
|
||||
? method.DeclaringType.GetGenericArguments()
|
||||
: null;
|
||||
Type[]? methodArguments = method.IsGenericMethod
|
||||
? method.GetGenericArguments()
|
||||
: null;
|
||||
var calls = new List<CompiledCall>();
|
||||
|
||||
for (int cursor = 0; cursor < il.Length;)
|
||||
{
|
||||
int instructionOffset = cursor;
|
||||
OpCode opCode = ReadOpCode(il, ref cursor);
|
||||
if (opCode.OperandType == OperandType.InlineMethod)
|
||||
{
|
||||
int token = BitConverter.ToInt32(il, cursor);
|
||||
MethodBase? target = method.Module.ResolveMethod(
|
||||
token,
|
||||
declaringArguments,
|
||||
methodArguments);
|
||||
if (target is not null && opCode is { Value: var value }
|
||||
&& value is 0x28 or 0x6F or 0x73)
|
||||
{
|
||||
calls.Add(new CompiledCall(instructionOffset, target));
|
||||
}
|
||||
}
|
||||
|
||||
cursor += OperandSize(opCode.OperandType, il, cursor);
|
||||
}
|
||||
|
||||
return calls;
|
||||
}
|
||||
|
||||
public static int IndexOf(
|
||||
IReadOnlyList<CompiledCall> calls,
|
||||
Type declaringType,
|
||||
string methodName,
|
||||
int startIndex = 0) =>
|
||||
Enumerable.Range(startIndex, calls.Count - startIndex)
|
||||
.FirstOrDefault(
|
||||
index => calls[index].Target.DeclaringType == declaringType
|
||||
&& calls[index].Target.Name == methodName,
|
||||
-1);
|
||||
|
||||
private static OpCode ReadOpCode(byte[] il, ref int cursor)
|
||||
{
|
||||
byte first = il[cursor++];
|
||||
short value = first == 0xFE
|
||||
? unchecked((short)(0xFE00 | il[cursor++]))
|
||||
: first;
|
||||
return OpCodesByValue.TryGetValue(value, out OpCode opCode)
|
||||
? opCode
|
||||
: throw new InvalidOperationException(
|
||||
$"Unknown IL opcode 0x{unchecked((ushort)value):X4}.");
|
||||
}
|
||||
|
||||
private static int OperandSize(OperandType operandType, byte[] il, int cursor) =>
|
||||
operandType switch
|
||||
{
|
||||
OperandType.InlineNone => 0,
|
||||
OperandType.ShortInlineBrTarget or
|
||||
OperandType.ShortInlineI or
|
||||
OperandType.ShortInlineVar => 1,
|
||||
OperandType.InlineVar => 2,
|
||||
OperandType.InlineBrTarget or
|
||||
OperandType.InlineField or
|
||||
OperandType.InlineI or
|
||||
OperandType.InlineMethod or
|
||||
OperandType.InlineSig or
|
||||
OperandType.InlineString or
|
||||
OperandType.InlineTok or
|
||||
OperandType.InlineType or
|
||||
OperandType.ShortInlineR => 4,
|
||||
OperandType.InlineI8 or OperandType.InlineR => 8,
|
||||
OperandType.InlineSwitch =>
|
||||
sizeof(int) + (BitConverter.ToInt32(il, cursor) * sizeof(int)),
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(operandType),
|
||||
operandType,
|
||||
"Unsupported IL operand type."),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue