using System.Buffers.Binary;
using System.Collections.Immutable;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
namespace XFESystem.ManagedRuntime;
///
/// Loads an actual CLI PE image and interprets a deliberately bounded console IL profile.
/// Unknown instructions, exception regions, local calls and native/instance calls are rejected.
/// This does not load the program into the host CLR and does not execute native image bytes.
///
public sealed class ManagedApplicationRunner
{
public ManagedExecutionResult Execute(ReadOnlyMemory image, IManagedConsole console, int instructionLimit = ManagedRuntimeCapabilities.MaxInstructions)
{
ArgumentNullException.ThrowIfNull(console);
if (image.Length is < 128 or > ManagedRuntimeCapabilities.MaxImageBytes) throw new BadImageFormatException("Managed image size is outside the accepted bounds.");
if (instructionLimit is < 1 or > ManagedRuntimeCapabilities.MaxInstructions) throw new ArgumentOutOfRangeException(nameof(instructionLimit));
using var pe = new PEReader(ImmutableArray.Create(image.ToArray()));
if (!pe.HasMetadata || pe.PEHeaders.CorHeader is not { } header || (header.Flags & CorFlags.ILOnly) == 0 || (header.Flags & CorFlags.NativeEntryPoint) != 0)
throw new BadImageFormatException("Expected an IL-only managed PE executable.");
var metadata = pe.GetMetadataReader();
if (!metadata.IsAssembly) throw new BadImageFormatException("A netmodule cannot be launched.");
var assembly = metadata.GetAssemblyDefinition();
if (!IsNet10(metadata, assembly)) throw new NotSupportedException("This profile accepts net10.0 programs only.");
var entry = MetadataTokens.EntityHandle(header.EntryPointTokenOrRelativeVirtualAddress);
if (entry.Kind != HandleKind.MethodDefinition) throw new BadImageFormatException("A managed entry point is required.");
var method = metadata.GetMethodDefinition((MethodDefinitionHandle)entry);
if ((method.Attributes & MethodAttributes.Static) == 0 || (method.Attributes & MethodAttributes.PinvokeImpl) != 0)
throw new NotSupportedException("The entry point must be a static managed method.");
var entrySignature = metadata.GetBlobReader(method.Signature);
var entryHeader = entrySignature.ReadSignatureHeader();
if (entryHeader.IsInstance || entryHeader.IsGeneric) throw new BadImageFormatException("Invalid entry signature.");
int entryArguments = entrySignature.ReadCompressedInteger();
var entryReturn = entrySignature.ReadSignatureTypeCode();
if (entryReturn is not (SignatureTypeCode.Void or SignatureTypeCode.Int32) || entryArguments is < 0 or > 1)
throw new NotSupportedException("Expected Main() or Main(string[]) returning void or Int32.");
if (entryArguments == 1 && (entrySignature.ReadSignatureTypeCode() != SignatureTypeCode.SZArray || entrySignature.ReadSignatureTypeCode() != SignatureTypeCode.String))
throw new NotSupportedException("Main arguments must be string[].");
if (entrySignature.RemainingBytes != 0) throw new BadImageFormatException("Trailing entry signature bytes.");
var body = pe.GetMethodBody(method.RelativeVirtualAddress);
if (body.ExceptionRegions.Length != 0) throw new NotSupportedException("Exception-handling IL is outside this initial compatibility profile.");
var code = body.GetILBytes() ?? throw new BadImageFormatException("Missing method body.");
ValidateInstructions(code);
var stack = new object?[Math.Min(body.MaxStack, 256)]; int count = 0, pc = 0, executed = 0, calls = 0;
if (body.MaxStack > 256) throw new NotSupportedException("Evaluation stack limit exceeded.");
var locals = new object?[LocalCount(metadata, body.LocalSignature)];
int stringCharacters = 0;
void ChargeString(int length)
{
if (length > ManagedRuntimeCapabilities.MaxStringCharacters || length > ManagedRuntimeCapabilities.MaxTotalStringCharacters - stringCharacters)
throw new InvalidOperationException("Managed string allocation limit exceeded.");
stringCharacters += length;
}
object? Pop() { if (count == 0) throw new BadImageFormatException("Evaluation stack underflow."); var value = stack[--count]; stack[count] = null; return value; }
void Push(object? value) { if (count >= stack.Length) throw new BadImageFormatException("Evaluation stack overflow."); stack[count++] = value; }
int Number(object? value) => value is int number ? number : throw new NotSupportedException("Expected an Int32 stack value.");
while (pc < code.Length)
{
if (++executed > instructionLimit) throw new InvalidOperationException("Managed instruction limit exceeded.");
byte op = code[pc++];
if (op == 0x00) continue; // nop
if (op == 0x02) { if (entryArguments != 1) throw new BadImageFormatException("No argument exists."); Push(Array.Empty()); continue; }
if (op is >= 0x06 and <= 0x09) { Push(Local(locals, op - 0x06)); continue; }
if (op is >= 0x0A and <= 0x0D) { SetLocal(locals, op - 0x0A, Pop()); continue; }
if (op == 0x11) { Push(Local(locals, code[pc++])); continue; }
if (op == 0x13) { SetLocal(locals, code[pc++], Pop()); continue; }
if (op == 0x14) { Push(null); continue; }
if (op is >= 0x15 and <= 0x1E) { Push(op - 0x16); continue; }
if (op == 0x1F) { Push((int)(sbyte)code[pc++]); continue; }
if (op == 0x20) { Push(ReadInt(code, ref pc)); continue; }
if (op == 0x25) { var value = Pop(); Push(value); Push(value); continue; }
if (op == 0x26) { Pop(); continue; }
if (op == 0x72)
{
int token = ReadInt(code, ref pc);
if ((token & unchecked((int)0xFF000000)) != 0x70000000) throw new BadImageFormatException("Invalid user string token.");
var value = metadata.GetUserString(MetadataTokens.UserStringHandle(token & 0xFFFFFF));
if (value.Length > 16_384) throw new NotSupportedException("Managed string literal limit exceeded.");
ChargeString(value.Length);
Push(value); continue;
}
if (op == 0x28) { int token = ReadInt(code, ref pc); Call(metadata, token, console, Pop, Push, ChargeString); calls++; continue; }
if (op == 0x2A)
{
if (count != (entryReturn == SignatureTypeCode.Void ? 0 : 1)) throw new BadImageFormatException("Unexpected stack at ret.");
return new(metadata.GetString(assembly.Name), executed, entryReturn == SignatureTypeCode.Void ? 0 : Number(Pop()), calls);
}
if (op is >= 0x58 and <= 0x5A)
{
int right = Number(Pop()), left = Number(Pop());
Push(op == 0x58 ? unchecked(left + right) : op == 0x59 ? unchecked(left - right) : unchecked(left * right)); continue;
}
if (op is >= 0x2B and <= 0x33 || op is >= 0x38 and <= 0x40)
{
bool shortForm = op < 0x38;
int kind = shortForm ? op - 0x2B : op - 0x38;
int offset = shortForm ? (sbyte)code[pc++] : ReadInt(code, ref pc);
bool jump;
if (kind == 0) jump = true;
else if (kind is 1 or 2) { var value = Pop(); bool truth = value is not null && (value is not int number || number != 0); jump = kind == 2 ? truth : !truth; }
else
{
int right = Number(Pop()), left = Number(Pop());
jump = kind switch { 3 => left == right, 4 => left >= right, 5 => left > right, 6 => left <= right, 7 => left < right, 8 => left != right, _ => false };
}
if (jump) pc += offset;
continue;
}
throw new NotSupportedException($"Unsupported IL opcode 0x{op:X2}.");
}
throw new BadImageFormatException("Method fell through without ret.");
}
private static void Call(MetadataReader reader, int token, IManagedConsole console, Func