Extremely messy start
This commit is contained in:
Executable
+8
@@ -0,0 +1,8 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
*.dll
|
||||
*.class
|
||||
*.java
|
||||
Executable
+789
@@ -0,0 +1,789 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JCIL.Core;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI;
|
||||
|
||||
public sealed class AssemblyBuilder
|
||||
{
|
||||
public readonly System.Reflection.Emit.AssemblyBuilder Assembly;
|
||||
private readonly ModuleBuilder _module;
|
||||
private readonly ConcurrentDictionary<Class, Type> _classes = [];
|
||||
private readonly ConcurrentDictionary<Field, FieldInfo> _fields = [];
|
||||
private readonly ConcurrentDictionary<Method, LazyMethod> _methods = [];
|
||||
private readonly MethodInfo _getType;
|
||||
|
||||
public int CompiledMethods { get; private set; }
|
||||
public int TotalMethods => _methods.Count;
|
||||
|
||||
public AssemblyBuilder(AssemblyName name, bool runnable)
|
||||
{
|
||||
Assembly = runnable
|
||||
? System.Reflection.Emit.AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndCollect)
|
||||
: new PersistedAssemblyBuilder(name, typeof(int).Assembly);
|
||||
|
||||
_module = Assembly.DefineDynamicModule("JCIL_Output");
|
||||
_getType = typeof(object).GetMethod(nameof(object.GetType), BindingFlags.Instance | BindingFlags.Public)!;
|
||||
}
|
||||
|
||||
public void ForceBuildMethod(Method method)
|
||||
{
|
||||
_ = _methods[method].Method;
|
||||
}
|
||||
|
||||
public Type Build(Class javaClass)
|
||||
{
|
||||
switch (javaClass.SpecialClassMetadata)
|
||||
{
|
||||
case IntrinsicMetadata { Type: IntrinsicType.Boolean }: return typeof(bool);
|
||||
case IntrinsicMetadata { Type: IntrinsicType.Byte }: return typeof(byte);
|
||||
case IntrinsicMetadata { Type: IntrinsicType.Char }: return typeof(char);
|
||||
case IntrinsicMetadata { Type: IntrinsicType.Short }: return typeof(short);
|
||||
case IntrinsicMetadata { Type: IntrinsicType.Int }: return typeof(int);
|
||||
case IntrinsicMetadata { Type: IntrinsicType.Long }: return typeof(long);
|
||||
case IntrinsicMetadata { Type: IntrinsicType.Float }: return typeof(float);
|
||||
case IntrinsicMetadata { Type: IntrinsicType.Double }: return typeof(double);
|
||||
case IntrinsicMetadata { Type: IntrinsicType.Void }: return typeof(void);
|
||||
case ArrayMetadata { ElementClass: var elementClass }: return Build(elementClass).MakeArrayType();
|
||||
}
|
||||
|
||||
var build = false;
|
||||
var theType = _classes.GetOrAdd(javaClass, _ =>
|
||||
{
|
||||
TypeAttributes flags = default;
|
||||
if ((javaClass.AccessFlags & ClassAccessFlags.Public) != 0) flags |= TypeAttributes.Public;
|
||||
if ((javaClass.AccessFlags & ClassAccessFlags.Abstract) != 0) flags |= TypeAttributes.Abstract;
|
||||
if ((javaClass.AccessFlags & ClassAccessFlags.Final) != 0) flags |= TypeAttributes.Sealed;
|
||||
if ((javaClass.AccessFlags & ClassAccessFlags.Interface) != 0) flags |= TypeAttributes.Interface;
|
||||
|
||||
var name = javaClass.Namespace.Length == 0
|
||||
? javaClass.Name.ToString()
|
||||
: $"{javaClass.Namespace.ToString().Replace('/', '.')}.{javaClass.Name}";
|
||||
|
||||
build = true;
|
||||
return _module.DefineType(name, flags);
|
||||
});
|
||||
|
||||
if (build && theType is TypeBuilder typeBuilder)
|
||||
{
|
||||
// Console.WriteLine($"Building type `{typeBuilder.FullName}`");
|
||||
|
||||
if (!theType.IsInterface && javaClass.SuperClass is not null && javaClass.SuperClass != javaClass)
|
||||
typeBuilder.SetParent(Build(javaClass.SuperClass));
|
||||
|
||||
foreach (var field in javaClass.Fields)
|
||||
{
|
||||
FieldAttributes flags = default;
|
||||
if ((field.AccessFlags & FieldAccessFlags.Static) != 0) flags |= FieldAttributes.Static;
|
||||
if ((field.AccessFlags & FieldAccessFlags.Final) != 0) flags |= FieldAttributes.InitOnly;
|
||||
if ((field.AccessFlags & FieldAccessFlags.Public) != 0) flags |= FieldAttributes.Public;
|
||||
if ((field.AccessFlags & FieldAccessFlags.Private) != 0) flags |= FieldAttributes.Private;
|
||||
if ((field.AccessFlags & FieldAccessFlags.Protected) != 0) flags |= FieldAttributes.Family;
|
||||
|
||||
var type = Build(field.Type);
|
||||
_fields.TryAdd(field, typeBuilder.DefineField(field.Name, type, flags));
|
||||
}
|
||||
|
||||
foreach (var method in javaClass.Methods)
|
||||
{
|
||||
MethodAttributes flags = default;
|
||||
if ((method.AccessFlags & MethodAccessFlags.Static) != 0) flags |= MethodAttributes.Static;
|
||||
else flags |= MethodAttributes.Virtual;
|
||||
if ((method.AccessFlags & MethodAccessFlags.Public) != 0) flags |= MethodAttributes.Public;
|
||||
if ((method.AccessFlags & MethodAccessFlags.Private) != 0) flags |= MethodAttributes.Private;
|
||||
if ((method.AccessFlags & MethodAccessFlags.Protected) != 0) flags |= MethodAttributes.Family;
|
||||
if ((method.AccessFlags & MethodAccessFlags.Abstract) != 0) flags |= MethodAttributes.Abstract;
|
||||
if ((method.AccessFlags & MethodAccessFlags.Final) != 0) flags |= MethodAttributes.Final;
|
||||
|
||||
var signature = (MethodSignature)method.Type.SpecialClassMetadata!;
|
||||
var paramTypes = signature.ParamTypes.Select(Build).ToArray();
|
||||
if (method.Name == "<init>")
|
||||
{
|
||||
flags &= ~MethodAttributes.Virtual;
|
||||
var ctor = typeBuilder.DefineConstructor(flags, CallingConventions.Standard, paramTypes);
|
||||
if (method.Attributes.OfType<MethodParameters>().FirstOrDefault() is { } attrib)
|
||||
{
|
||||
for (var j = 0; j < attrib.Parameters.Count; j++)
|
||||
{
|
||||
// TODO populate parameter attributes
|
||||
ctor.DefineParameter(j, ParameterAttributes.None, attrib.Parameters[j].Item1);
|
||||
}
|
||||
}
|
||||
|
||||
_methods.TryAdd(method, new LazyMethod(ctor, () =>
|
||||
{
|
||||
Console.WriteLine($"Building method `{typeBuilder.FullName}.{method.Name}`");
|
||||
if (method.TryGetCode(out var code, out var opcodes))
|
||||
CompileOpCodes(javaClass, ctor, ctor.GetILGenerator(), code, opcodes);
|
||||
}));
|
||||
}
|
||||
else if (method.Name == "<clinit>")
|
||||
{
|
||||
var ctor = typeBuilder.DefineTypeInitializer();
|
||||
if (method.Attributes.OfType<MethodParameters>().FirstOrDefault() is { } attrib)
|
||||
{
|
||||
for (var j = 0; j < attrib.Parameters.Count; j++)
|
||||
{
|
||||
// TODO populate parameter attributes
|
||||
ctor.DefineParameter(j, ParameterAttributes.None, attrib.Parameters[j].Item1);
|
||||
}
|
||||
}
|
||||
|
||||
_methods.TryAdd(method, new LazyMethod(ctor, () =>
|
||||
{
|
||||
Console.WriteLine($"Building method `{typeBuilder.FullName}.{method.Name}`");
|
||||
if (method.TryGetCode(out var code, out var opcodes))
|
||||
CompileOpCodes(javaClass, ctor, ctor.GetILGenerator(), code, opcodes);
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
var methodBuilder = typeBuilder.DefineMethod(method.Name, flags);
|
||||
methodBuilder.SetReturnType(Build(signature.ReturnType));
|
||||
methodBuilder.SetParameters(paramTypes);
|
||||
|
||||
if (method.Attributes.OfType<MethodParameters>().FirstOrDefault() is { } attrib)
|
||||
{
|
||||
for (var j = 0; j < attrib.Parameters.Count; j++)
|
||||
{
|
||||
// TODO populate parameter attributes
|
||||
methodBuilder.DefineParameter(j, ParameterAttributes.None, attrib.Parameters[j].Item1);
|
||||
}
|
||||
}
|
||||
|
||||
_methods.TryAdd(method, new LazyMethod(methodBuilder, () =>
|
||||
{
|
||||
Console.WriteLine($"Building method `{typeBuilder.FullName}.{method.Name}`");
|
||||
if (method.TryGetCode(out var code, out var opcodes))
|
||||
CompileOpCodes(javaClass, methodBuilder, methodBuilder.GetILGenerator(), code, opcodes);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return typeBuilder.CreateType();
|
||||
}
|
||||
|
||||
foreach (var interfaceClass in javaClass.Interfaces)
|
||||
_ = Build(interfaceClass);
|
||||
|
||||
return theType;
|
||||
}
|
||||
|
||||
private void CompileOpCodes(Class javaClass, MethodBase methodBase, ILGenerator il, Code code, OpCodeReader opcodes)
|
||||
{
|
||||
var locals = new (Type, int)[code.MaxLocals];
|
||||
var localsCount = 0;
|
||||
var localsIdx = 0;
|
||||
|
||||
if (!methodBase.IsStatic)
|
||||
{
|
||||
il.DeclareLocal(methodBase.DeclaringType!);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Stloc_0);
|
||||
locals[localsIdx++] = (methodBase.DeclaringType!, localsCount++);
|
||||
}
|
||||
|
||||
var parameters = methodBase.GetParameters();
|
||||
for (int i = 0, j = methodBase.IsStatic ? 0 : 1; i < parameters.Length; i++, j++)
|
||||
{
|
||||
il.DeclareLocal(parameters[i].ParameterType);
|
||||
il.Emit(OpCodes.Ldarg, (short) j);
|
||||
il.Emit(OpCodes.Stloc, (short) j);
|
||||
locals[localsIdx++] = (parameters[i].ParameterType, localsCount++);
|
||||
}
|
||||
|
||||
if (code.Attributes.OfType<StackMapTable>().FirstOrDefault() is not { Frames: var frames })
|
||||
frames = [];
|
||||
|
||||
var position = opcodes.Reader.Stream.Position;
|
||||
var labels = new Dictionary<long, Label>();
|
||||
while (opcodes.MoveNext())
|
||||
{
|
||||
labels.Add(position, il.DefineLabel());
|
||||
position = opcodes.Reader.Stream.Position;
|
||||
}
|
||||
|
||||
opcodes.Reset();
|
||||
position = opcodes.Reader.Stream.Position;
|
||||
|
||||
void AddLocal(in StackMapTable.VerificationTypeInfo info)
|
||||
{
|
||||
Type compiledType;
|
||||
switch (info.Tag)
|
||||
{
|
||||
case StackMapTable.VerificationTypeInfoTag.Top:
|
||||
locals[localsIdx++] = default;
|
||||
return;
|
||||
|
||||
case StackMapTable.VerificationTypeInfoTag.Integer: compiledType = typeof(int); break;
|
||||
case StackMapTable.VerificationTypeInfoTag.Long: compiledType = typeof(long); break;
|
||||
case StackMapTable.VerificationTypeInfoTag.Float: compiledType = typeof(float); break;
|
||||
case StackMapTable.VerificationTypeInfoTag.Double: compiledType = typeof(double); break;
|
||||
|
||||
case StackMapTable.VerificationTypeInfoTag.Object:
|
||||
{
|
||||
var type = (Class)javaClass.GetConstant(info.Parameter)!;
|
||||
compiledType = Build(type);
|
||||
break;
|
||||
}
|
||||
|
||||
case StackMapTable.VerificationTypeInfoTag.UninitializedThis:
|
||||
compiledType = methodBase.DeclaringType!;
|
||||
break;
|
||||
|
||||
default: throw new NotImplementedException($"Unimplemented local tag {info.Tag}");
|
||||
}
|
||||
|
||||
locals[localsIdx++] = (compiledType, localsCount++);
|
||||
il.DeclareLocal(compiledType);
|
||||
}
|
||||
|
||||
for (var f = -1; opcodes.MoveNext();)
|
||||
{
|
||||
if (f + 1 < frames.Count && position >= frames[f + 1].StartOffset)
|
||||
{
|
||||
switch (frames[++f])
|
||||
{
|
||||
case StackMapTable.SameFrame: break;
|
||||
case StackMapTable.SameFrameExtended: break;
|
||||
case StackMapTable.OneLocalFrame: break;
|
||||
case StackMapTable.OneLocalFrameExtended: break;
|
||||
|
||||
case StackMapTable.AppendFrame frame:
|
||||
foreach (var info in frame.Locals) AddLocal(info);
|
||||
break;
|
||||
|
||||
case StackMapTable.ChopFrame frame:
|
||||
localsIdx -= frame.Count;
|
||||
break;
|
||||
|
||||
case StackMapTable.FullFrame frame:
|
||||
{
|
||||
localsIdx = 0;
|
||||
foreach (var info in frame.Locals) AddLocal(info);
|
||||
break;
|
||||
}
|
||||
|
||||
default: throw new NotImplementedException($"Unimplemented {frames[f]}");
|
||||
}
|
||||
}
|
||||
|
||||
il.MarkLabel(labels[position]);
|
||||
var opcode = opcodes.Current;
|
||||
|
||||
Label JmpLabel()
|
||||
{
|
||||
return labels[position + (short)opcode.P0.UShort];
|
||||
}
|
||||
|
||||
void LoadLocal(int idx)
|
||||
{
|
||||
var loc = locals[idx].Item2;
|
||||
switch (loc)
|
||||
{
|
||||
case 0: il.Emit(OpCodes.Ldloc_0); break;
|
||||
case 1: il.Emit(OpCodes.Ldloc_1); break;
|
||||
case 2: il.Emit(OpCodes.Ldloc_2); break;
|
||||
case 3: il.Emit(OpCodes.Ldloc_3); break;
|
||||
case <= 255: il.Emit(OpCodes.Ldloc_S, (byte)loc); break;
|
||||
default: il.Emit(OpCodes.Ldloc, (short)loc); break;
|
||||
}
|
||||
}
|
||||
|
||||
void StoreLocal(Type ty, int idx)
|
||||
{
|
||||
ref var loc = ref locals[idx];
|
||||
if (loc.Item2 == -1 || loc.Item1 is null || (loc.Item1 != ty && (loc.Item1.IsValueType || ty.IsValueType)))
|
||||
{
|
||||
il.DeclareLocal(ty);
|
||||
loc = (ty, localsCount++);
|
||||
}
|
||||
|
||||
switch (loc.Item2)
|
||||
{
|
||||
case 0: il.Emit(OpCodes.Stloc_0); break;
|
||||
case 1: il.Emit(OpCodes.Stloc_1); break;
|
||||
case 2: il.Emit(OpCodes.Stloc_2); break;
|
||||
case 3: il.Emit(OpCodes.Stloc_3); break;
|
||||
case <= 255: il.Emit(OpCodes.Stloc_S, (byte)loc.Item2); break;
|
||||
default: il.Emit(OpCodes.Stloc, (short)loc.Item2); break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (opcode.Mnemonic)
|
||||
{
|
||||
case OpCodeMnemonic.Nop: break;
|
||||
case OpCodeMnemonic.AConstNull: il.Emit(OpCodes.Ldnull); break;
|
||||
case OpCodeMnemonic.IConstM1: il.Emit(OpCodes.Ldc_I4_M1); break;
|
||||
case OpCodeMnemonic.IConst0: il.Emit(OpCodes.Ldc_I4_0); break;
|
||||
case OpCodeMnemonic.IConst1: il.Emit(OpCodes.Ldc_I4_1); break;
|
||||
case OpCodeMnemonic.IConst2: il.Emit(OpCodes.Ldc_I4_2); break;
|
||||
case OpCodeMnemonic.IConst3: il.Emit(OpCodes.Ldc_I4_3); break;
|
||||
case OpCodeMnemonic.IConst4: il.Emit(OpCodes.Ldc_I4_4); break;
|
||||
case OpCodeMnemonic.IConst5: il.Emit(OpCodes.Ldc_I4_5); break;
|
||||
case OpCodeMnemonic.LConst0: il.Emit(OpCodes.Ldc_I8, 0L); break;
|
||||
case OpCodeMnemonic.LConst1: il.Emit(OpCodes.Ldc_I8, 1L); break;
|
||||
case OpCodeMnemonic.FConst0: il.Emit(OpCodes.Ldc_R4, 0f); break;
|
||||
case OpCodeMnemonic.FConst1: il.Emit(OpCodes.Ldc_R4, 1f); break;
|
||||
case OpCodeMnemonic.FConst2: il.Emit(OpCodes.Ldc_R4, 2f); break;
|
||||
case OpCodeMnemonic.DConst0: il.Emit(OpCodes.Ldc_R4, 0d); break;
|
||||
case OpCodeMnemonic.DConst1: il.Emit(OpCodes.Ldc_R4, 1d); break;
|
||||
|
||||
case OpCodeMnemonic.PushByte: il.Emit(OpCodes.Ldc_I4, (int)opcode.P0.Byte); break;
|
||||
case OpCodeMnemonic.PushShort: il.Emit(OpCodes.Ldc_I4, (int)(short)opcode.P0.UShort); break;
|
||||
|
||||
case OpCodeMnemonic.Ldc:
|
||||
case OpCodeMnemonic.LdcW:
|
||||
case OpCodeMnemonic.Ldc2W:
|
||||
{
|
||||
var constant = javaClass.GetConstant(opcode.P0.UShort);
|
||||
switch (constant)
|
||||
{
|
||||
//TODO this is wrong
|
||||
case Class c: il.Emit(OpCodes.Ldtoken, Build(c)); break;
|
||||
|
||||
case int val: il.Emit(OpCodes.Ldc_I4, val); break;
|
||||
case short val: il.Emit(OpCodes.Ldc_I4, (int) val); break;
|
||||
case long val: il.Emit(OpCodes.Ldc_I8, val); break;
|
||||
case float val: il.Emit(OpCodes.Ldc_R4, val); break;
|
||||
case double val: il.Emit(OpCodes.Ldc_R8, val); break;
|
||||
|
||||
case string str: il.Emit(OpCodes.Ldstr, str); break;
|
||||
default:
|
||||
throw new NotImplementedException(
|
||||
$"Unimplemented ldc {constant?.GetType().FullName ?? "null"}");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.ILoad:
|
||||
case OpCodeMnemonic.LLoad:
|
||||
case OpCodeMnemonic.ALoad: LoadLocal(opcode.P0.Byte); break;
|
||||
|
||||
case OpCodeMnemonic.ILoad0:
|
||||
case OpCodeMnemonic.LLoad0:
|
||||
case OpCodeMnemonic.FLoad0:
|
||||
case OpCodeMnemonic.DLoad0:
|
||||
case OpCodeMnemonic.ALoad0: LoadLocal(0); break;
|
||||
case OpCodeMnemonic.ALoad1:
|
||||
case OpCodeMnemonic.LLoad1:
|
||||
case OpCodeMnemonic.FLoad1:
|
||||
case OpCodeMnemonic.DLoad1:
|
||||
case OpCodeMnemonic.ILoad1: LoadLocal(1); break;
|
||||
case OpCodeMnemonic.ALoad2:
|
||||
case OpCodeMnemonic.LLoad2:
|
||||
case OpCodeMnemonic.FLoad2:
|
||||
case OpCodeMnemonic.DLoad2:
|
||||
case OpCodeMnemonic.ILoad2: LoadLocal(2); break;
|
||||
case OpCodeMnemonic.ALoad3:
|
||||
case OpCodeMnemonic.LLoad3:
|
||||
case OpCodeMnemonic.FLoad3:
|
||||
case OpCodeMnemonic.DLoad3:
|
||||
case OpCodeMnemonic.ILoad3: LoadLocal(3); break;
|
||||
|
||||
case OpCodeMnemonic.IALoad: il.Emit(OpCodes.Ldelem_I4); break;
|
||||
case OpCodeMnemonic.LALoad: il.Emit(OpCodes.Ldelem_I8); break;
|
||||
case OpCodeMnemonic.AALoad: il.Emit(OpCodes.Ldelem_Ref); break;
|
||||
case OpCodeMnemonic.BALoad: il.Emit(OpCodes.Ldelem_I1); break;
|
||||
case OpCodeMnemonic.CALoad: il.Emit(OpCodes.Ldelem_I2); break;
|
||||
case OpCodeMnemonic.SALoad: il.Emit(OpCodes.Ldelem_I2); break;
|
||||
|
||||
case OpCodeMnemonic.AStore: StoreLocal(typeof(object), opcode.P0.Byte); break;
|
||||
case OpCodeMnemonic.IStore: StoreLocal(typeof(int), opcode.P0.Byte); break;
|
||||
case OpCodeMnemonic.LStore: StoreLocal(typeof(long), opcode.P0.Byte); break;
|
||||
case OpCodeMnemonic.FStore: StoreLocal(typeof(float), opcode.P0.Byte); break;
|
||||
case OpCodeMnemonic.DStore: StoreLocal(typeof(double), opcode.P0.Byte); break;
|
||||
|
||||
case OpCodeMnemonic.AStore0: StoreLocal(typeof(object), 0); break;
|
||||
case OpCodeMnemonic.IStore0: StoreLocal(typeof(int), 0); break;
|
||||
case OpCodeMnemonic.LStore0: StoreLocal(typeof(long), 0); break;
|
||||
case OpCodeMnemonic.AStore1:StoreLocal(typeof(object), 1); break;
|
||||
case OpCodeMnemonic.IStore1:StoreLocal(typeof(int), 1); break;
|
||||
case OpCodeMnemonic.LStore1:StoreLocal(typeof(long), 1); break;
|
||||
case OpCodeMnemonic.AStore2:StoreLocal(typeof(object), 2); break;
|
||||
case OpCodeMnemonic.IStore2:StoreLocal(typeof(int), 2); break;
|
||||
case OpCodeMnemonic.LStore2:StoreLocal(typeof(long), 2); break;
|
||||
case OpCodeMnemonic.AStore3:StoreLocal(typeof(object), 3); break;
|
||||
case OpCodeMnemonic.IStore3:StoreLocal(typeof(int), 3); break;
|
||||
case OpCodeMnemonic.LStore3:StoreLocal(typeof(long), 3); break;
|
||||
|
||||
case OpCodeMnemonic.LAStore: il.Emit(OpCodes.Stelem_I8); break;
|
||||
case OpCodeMnemonic.AAStore: il.Emit(OpCodes.Stelem_Ref); break;
|
||||
case OpCodeMnemonic.BAStore: il.Emit(OpCodes.Stelem_I1); break;
|
||||
case OpCodeMnemonic.CAStore: il.Emit(OpCodes.Stelem_I2); break;
|
||||
|
||||
|
||||
case OpCodeMnemonic.Pop: il.Emit(OpCodes.Pop); break;
|
||||
case OpCodeMnemonic.Pop2:
|
||||
{
|
||||
var class1Label = il.DefineLabel();
|
||||
var class2Label = il.DefineLabel();
|
||||
var continueLabel = il.DefineLabel();
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.EmitCall(OpCodes.Call, _getType, null);
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Ldtoken, typeof(long).MetadataToken);
|
||||
il.Emit(OpCodes.Pop);
|
||||
il.Emit(OpCodes.Beq, class2Label);
|
||||
il.Emit(OpCodes.Ldtoken, typeof(double).MetadataToken);
|
||||
il.Emit(OpCodes.Beq, class2Label);
|
||||
il.MarkLabel(class1Label);
|
||||
il.Emit(OpCodes.Pop);
|
||||
il.Emit(OpCodes.Pop);
|
||||
il.Emit(OpCodes.Br, continueLabel);
|
||||
il.MarkLabel(class2Label);
|
||||
il.Emit(OpCodes.Pop);
|
||||
il.Emit(OpCodes.Br, continueLabel);
|
||||
il.MarkLabel(continueLabel);
|
||||
break;
|
||||
}
|
||||
case OpCodeMnemonic.Dup: il.Emit(OpCodes.Dup); break;
|
||||
|
||||
case OpCodeMnemonic.IAdd:
|
||||
case OpCodeMnemonic.LAdd:
|
||||
case OpCodeMnemonic.FAdd:
|
||||
case OpCodeMnemonic.DAdd: il.Emit(OpCodes.Add); break;
|
||||
case OpCodeMnemonic.ISub:
|
||||
case OpCodeMnemonic.LSub:
|
||||
case OpCodeMnemonic.FSub:
|
||||
case OpCodeMnemonic.DSub: il.Emit(OpCodes.Sub); break;
|
||||
case OpCodeMnemonic.IMul:
|
||||
case OpCodeMnemonic.LMul:
|
||||
case OpCodeMnemonic.FMul:
|
||||
case OpCodeMnemonic.DMul: il.Emit(OpCodes.Mul); break;
|
||||
case OpCodeMnemonic.IDiv:
|
||||
case OpCodeMnemonic.LDiv:
|
||||
case OpCodeMnemonic.FDiv:
|
||||
case OpCodeMnemonic.DDiv: il.Emit(OpCodes.Div); break;
|
||||
case OpCodeMnemonic.IRem:
|
||||
case OpCodeMnemonic.LRem:
|
||||
case OpCodeMnemonic.FRem:
|
||||
case OpCodeMnemonic.DRem: il.Emit(OpCodes.Rem); break;
|
||||
case OpCodeMnemonic.INeg:
|
||||
case OpCodeMnemonic.LNeg: il.Emit(OpCodes.Neg); break;
|
||||
|
||||
case OpCodeMnemonic.IShl:
|
||||
case OpCodeMnemonic.LShl: il.Emit(OpCodes.Shl); break;
|
||||
case OpCodeMnemonic.IShr:
|
||||
case OpCodeMnemonic.LShr: il.Emit(OpCodes.Shr); break;
|
||||
case OpCodeMnemonic.IUShr:
|
||||
case OpCodeMnemonic.LUShr: il.Emit(OpCodes.Shr_Un); break;
|
||||
case OpCodeMnemonic.IAnd:
|
||||
case OpCodeMnemonic.LAnd: il.Emit(OpCodes.And); break;
|
||||
case OpCodeMnemonic.IOr:
|
||||
case OpCodeMnemonic.LOr: il.Emit(OpCodes.Or); break;
|
||||
|
||||
case OpCodeMnemonic.IInc:
|
||||
{
|
||||
LoadLocal(opcode.P0.UShort);
|
||||
il.Emit(OpCodes.Ldc_I4, opcode.P0.Byte);
|
||||
il.Emit(OpCodes.Add);
|
||||
StoreLocal(typeof(int), opcode.P0.UShort);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.IntToLong: il.Emit(OpCodes.Conv_I8); break;
|
||||
case OpCodeMnemonic.IntToByte: il.Emit(OpCodes.Conv_I1); break;
|
||||
case OpCodeMnemonic.IntToChar: il.Emit(OpCodes.Conv_U2); break;
|
||||
case OpCodeMnemonic.IntToShort: il.Emit(OpCodes.Conv_I2); break;
|
||||
|
||||
case OpCodeMnemonic.LCmp:
|
||||
{
|
||||
var func = ILHelpers.LCmp;
|
||||
il.EmitCall(OpCodes.Call, func.Method, null);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.InvokeSpecial:
|
||||
case OpCodeMnemonic.InvokeStatic:
|
||||
{
|
||||
var methodRef = (MethodRef)javaClass.GetConstant(opcode.P0.UShort)!;
|
||||
_ = Build(methodRef.Class);
|
||||
|
||||
var signature = (MethodSignature)javaClass.Loader.GetClass(methodRef.Method.Descriptor.AsMemory())
|
||||
.SpecialClassMetadata!;
|
||||
|
||||
var method = methodRef.Class.GetMethod(methodRef.Method.Name, signature);
|
||||
if (method is null || !_methods.TryGetValue(method, out var compiledMethod))
|
||||
throw new Exception($"Method {methodRef.Method.Name} not found");
|
||||
|
||||
if (compiledMethod.Method is MethodInfo methodInfo)
|
||||
il.EmitCall(OpCodes.Call, methodInfo, null);
|
||||
|
||||
if (compiledMethod.Method is ConstructorInfo constructorInfo)
|
||||
il.Emit(OpCodes.Call, constructorInfo);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.InvokeVirtual:
|
||||
{
|
||||
var methodRef = (MethodRef)javaClass.GetConstant(opcode.P0.UShort)!;
|
||||
_ = Build(methodRef.Class);
|
||||
|
||||
var signature = (MethodSignature)javaClass.Loader.GetClass(methodRef.Method.Descriptor.AsMemory())
|
||||
.SpecialClassMetadata!;
|
||||
|
||||
var method = methodRef.Class.GetMethod(methodRef.Method.Name, signature);
|
||||
if (method is null || !_methods.TryGetValue(method, out var compiledMethod))
|
||||
throw new Exception($"Method {methodRef.Method.Name} not found");
|
||||
|
||||
if (compiledMethod.Method is MethodInfo methodInfo)
|
||||
il.EmitCall(OpCodes.Callvirt, methodInfo, null);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.InvokeInterface:
|
||||
{
|
||||
var methodRef = (MethodRef)javaClass.GetConstant(opcode.P0.UShort)!;
|
||||
_ = Build(methodRef.Class);
|
||||
|
||||
var signature = (MethodSignature)javaClass.Loader.GetClass(methodRef.Method.Descriptor.AsMemory())
|
||||
.SpecialClassMetadata!;
|
||||
|
||||
var method = methodRef.Class.GetMethod(methodRef.Method.Name, signature);
|
||||
if (method is null || !_methods.TryGetValue(method, out var compiledMethod))
|
||||
throw new Exception($"Method {methodRef.Method.Name} not found");
|
||||
|
||||
if (compiledMethod.Method is MethodInfo methodInfo)
|
||||
il.EmitCall(OpCodes.Callvirt, methodInfo, null);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.New:
|
||||
{
|
||||
var obj = (Class)javaClass.GetConstant(opcode.P0.UShort)!;
|
||||
var objT = Build(obj);
|
||||
|
||||
// TODO Make this work with any number of parameters
|
||||
var posBackup = opcodes.Reader.Stream.Position;
|
||||
opcodes.MoveNext();
|
||||
if (opcodes.Current.Mnemonic == OpCodeMnemonic.Dup)
|
||||
{
|
||||
opcodes.MoveNext();
|
||||
if (opcodes.Current.Mnemonic == OpCodeMnemonic.InvokeSpecial)
|
||||
{
|
||||
var methodRef = (MethodRef)javaClass.GetConstant(opcodes.Current.P0.UShort)!;
|
||||
_ = Build(methodRef.Class);
|
||||
|
||||
var signature = (MethodSignature)javaClass.Loader
|
||||
.GetClass(methodRef.Method.Descriptor.AsMemory())
|
||||
.SpecialClassMetadata!;
|
||||
|
||||
var method = methodRef.Class.GetMethod(methodRef.Method.Name, signature);
|
||||
if (method is null || !_methods.TryGetValue(method, out var compiledMethod))
|
||||
throw new Exception($"Method {methodRef.Method.Name} not found");
|
||||
|
||||
if (compiledMethod.Method is ConstructorInfo constructorInfo)
|
||||
{
|
||||
il.Emit(OpCodes.Newobj, constructorInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opcodes.Reader.Stream.Position = posBackup;
|
||||
var func = RuntimeHelpers.GetUninitializedObject;
|
||||
il.Emit(OpCodes.Ldtoken, objT);
|
||||
il.Emit(OpCodes.Call, func.Method);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.NewArray:
|
||||
{
|
||||
il.Emit(OpCodes.Newarr, opcode.P0.Byte switch
|
||||
{
|
||||
4 => typeof(bool),
|
||||
5 => typeof(char),
|
||||
6 => typeof(float),
|
||||
7 => typeof(double),
|
||||
8 => typeof(byte),
|
||||
9 => typeof(short),
|
||||
10 => typeof(int),
|
||||
11 => typeof(long),
|
||||
_ => throw new NotImplementedException(nameof(opcode.P0)),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.ANewArray:
|
||||
{
|
||||
var elem = (Class)javaClass.GetConstant(opcode.P0.UShort)!;
|
||||
il.Emit(OpCodes.Newarr, Build(elem));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case OpCodeMnemonic.IfEq: il.Emit(OpCodes.Brfalse, JmpLabel()); break;
|
||||
case OpCodeMnemonic.IfNe: il.Emit(OpCodes.Brtrue, JmpLabel()); break;
|
||||
case OpCodeMnemonic.IfLt:
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
il.Emit(OpCodes.Blt, JmpLabel());
|
||||
break;
|
||||
case OpCodeMnemonic.IfLe:
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
il.Emit(OpCodes.Ble, JmpLabel());
|
||||
break;
|
||||
case OpCodeMnemonic.IfGe:
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
il.Emit(OpCodes.Bge, JmpLabel());
|
||||
break;
|
||||
case OpCodeMnemonic.IfGt:
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
il.Emit(OpCodes.Bgt, JmpLabel());
|
||||
break;
|
||||
|
||||
case OpCodeMnemonic.IfICmpEq: il.Emit(OpCodes.Beq, JmpLabel()); break;
|
||||
case OpCodeMnemonic.IfICmpNe: il.Emit(OpCodes.Bne_Un, JmpLabel()); break;
|
||||
case OpCodeMnemonic.IfICmpLt: il.Emit(OpCodes.Blt, JmpLabel()); break;
|
||||
case OpCodeMnemonic.IfICmpLe: il.Emit(OpCodes.Ble, JmpLabel()); break;
|
||||
case OpCodeMnemonic.IfICmpGe: il.Emit(OpCodes.Bge, JmpLabel()); break;
|
||||
case OpCodeMnemonic.IfICmpGt: il.Emit(OpCodes.Bgt, JmpLabel()); break;
|
||||
|
||||
case OpCodeMnemonic.IfACmpEq:
|
||||
{
|
||||
il.Emit(OpCodes.Ceq);
|
||||
il.Emit(OpCodes.Brtrue, JmpLabel());
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.IfACmpNe:
|
||||
{
|
||||
il.Emit(OpCodes.Ceq);
|
||||
il.Emit(OpCodes.Brfalse, JmpLabel());
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.Goto: il.Emit(OpCodes.Br, JmpLabel()); break;
|
||||
case OpCodeMnemonic.IfNull: il.Emit(OpCodes.Brfalse, JmpLabel()); break;
|
||||
case OpCodeMnemonic.IfNonNull: il.Emit(OpCodes.Brtrue, JmpLabel()); break;
|
||||
|
||||
case OpCodeMnemonic.Return:
|
||||
case OpCodeMnemonic.IReturn:
|
||||
case OpCodeMnemonic.LReturn:
|
||||
case OpCodeMnemonic.FReturn:
|
||||
case OpCodeMnemonic.DReturn:
|
||||
case OpCodeMnemonic.AReturn:
|
||||
{
|
||||
il.Emit(OpCodes.Ret);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.SetField:
|
||||
{
|
||||
var fieldRef = (FieldRef)javaClass.GetConstant(opcode.P0.UShort)!;
|
||||
_ = Build(fieldRef.Class);
|
||||
var field = fieldRef.Class.GetField(fieldRef.Field.Name)!;
|
||||
var compiledField = _fields[field];
|
||||
il.Emit(OpCodes.Stfld, compiledField);
|
||||
break;
|
||||
}
|
||||
case OpCodeMnemonic.SetStaticField:
|
||||
{
|
||||
var fieldRef = (FieldRef)javaClass.GetConstant(opcode.P0.UShort)!;
|
||||
_ = Build(fieldRef.Class);
|
||||
var field = fieldRef.Class.GetField(fieldRef.Field.Name)!;
|
||||
var compiledField = _fields[field];
|
||||
il.Emit(OpCodes.Stsfld, compiledField);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.GetField:
|
||||
{
|
||||
var fieldRef = (FieldRef)javaClass.GetConstant(opcode.P0.UShort)!;
|
||||
_ = Build(fieldRef.Class);
|
||||
var field = fieldRef.Class.GetField(fieldRef.Field.Name)!;
|
||||
var compiledField = _fields[field];
|
||||
il.Emit(OpCodes.Ldfld, compiledField);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.GetStaticField:
|
||||
{
|
||||
var fieldRef = (FieldRef)javaClass.GetConstant(opcode.P0.UShort)!;
|
||||
_ = Build(fieldRef.Class);
|
||||
var field = fieldRef.Class.GetField(fieldRef.Field.Name)!;
|
||||
var compiledField = _fields[field];
|
||||
il.Emit(OpCodes.Ldsfld, compiledField);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.ArrayLen:
|
||||
{
|
||||
il.Emit(OpCodes.Ldlen);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.AThrow:
|
||||
{
|
||||
il.Emit(OpCodes.Throw);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.InstanceOf:
|
||||
{
|
||||
var type = Build((Class)javaClass.GetConstant(opcode.P0.UShort)!);
|
||||
il.Emit(OpCodes.Isinst, type);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.CheckCast:
|
||||
{
|
||||
var type = Build((Class)javaClass.GetConstant(opcode.P0.UShort)!);
|
||||
il.Emit(OpCodes.Castclass, type);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.MonitorEnter:
|
||||
{
|
||||
Action<object> func = Monitor.Enter;
|
||||
il.EmitCall(OpCodes.Call, func.Method, null);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.MonitorExit:
|
||||
{
|
||||
Action<object> func = Monitor.Exit;
|
||||
il.EmitCall(OpCodes.Call, func.Method, null);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
il.Emit(OpCodes.Nop);
|
||||
Console.WriteLine($"Unknown opcode: {opcode.Mnemonic}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
position = opcodes.Reader.Stream.Position;
|
||||
}
|
||||
|
||||
CompiledMethods += 1;
|
||||
}
|
||||
}
|
||||
|
||||
internal class LazyMethod(MethodBase method, Action compile)
|
||||
{
|
||||
public bool Compiled { get; private set; }
|
||||
|
||||
public MethodBase Method
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Compiled)
|
||||
{
|
||||
Compiled = true;
|
||||
compile();
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>JCIL.CLI</AssemblyName>
|
||||
<RootNamespace>JCIL.CLI</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\JavaClass\JavaClass.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var loader = new TypeLoader();
|
||||
loader.AddPath(Environment.CurrentDirectory);
|
||||
loader.AddPath("../extracted_java_classes/java.base/");
|
||||
var javaClass = loader.LoadClass("Main".AsMemory());
|
||||
Console.WriteLine($"Load time: {(DateTime.Now - now).TotalMilliseconds}ms");
|
||||
|
||||
var builder = new AssemblyBuilder(new AssemblyName("Test"), false);
|
||||
var dotnetClass = builder.Build(javaClass);
|
||||
try
|
||||
{
|
||||
builder.ForceBuildMethod(javaClass.Methods[1]);
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
Console.WriteLine($"Compiled methods: {builder.CompiledMethods}/{builder.TotalMethods}");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Error.WriteLine(e.Message);
|
||||
Environment.Exit(1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.WriteLine($"Compiled methods: {builder.CompiledMethods}/{builder.TotalMethods}");
|
||||
var assembly = (PersistedAssemblyBuilder)builder.Assembly;
|
||||
assembly.Save("out.dll");
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace JCIL.Core;
|
||||
|
||||
public static class ILHelpers
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static int LCmp(long a, long b)
|
||||
{
|
||||
if (a > b) return 1;
|
||||
if (a < b) return -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JavaClass", "JavaClass\JavaClass.csproj", "{188B38E5-D715-4049-8E26-89072EE3A0FE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLI", "CLI\CLI.csproj", "{7B65983E-9D50-4BE6-A254-E233967956DF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JCIL.Core", "JCIL.Core\JCIL.Core.csproj", "{8EE41D1D-D947-4B54-A130-89E2F77C28D6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{188B38E5-D715-4049-8E26-89072EE3A0FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{188B38E5-D715-4049-8E26-89072EE3A0FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{188B38E5-D715-4049-8E26-89072EE3A0FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{188B38E5-D715-4049-8E26-89072EE3A0FE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7B65983E-9D50-4BE6-A254-E233967956DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7B65983E-9D50-4BE6-A254-E233967956DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7B65983E-9D50-4BE6-A254-E233967956DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7B65983E-9D50-4BE6-A254-E233967956DF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8EE41D1D-D947-4B54-A130-89E2F77C28D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8EE41D1D-D947-4B54-A130-89E2F77C28D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8EE41D1D-D947-4B54-A130-89E2F77C28D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8EE41D1D-D947-4B54-A130-89E2F77C28D6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Executable
+250
@@ -0,0 +1,250 @@
|
||||
namespace JCIL.Java.Class;
|
||||
|
||||
public abstract class Attribute
|
||||
{
|
||||
public static Attribute Read(Class parent, Reader reader)
|
||||
{
|
||||
var attrName = (string)parent.GetConstant(reader.ReadUInt16())!;
|
||||
var length = reader.ReadUInt32();
|
||||
|
||||
switch (attrName)
|
||||
{
|
||||
case "MethodParameters":
|
||||
{
|
||||
var count = reader.ReadUInt8();
|
||||
var args = new (string, ParameterAccessFlags)[count];
|
||||
foreach (ref var tuple in args.AsSpan())
|
||||
{
|
||||
tuple.Item1 = (string)parent.GetConstant(reader.ReadUInt16())!;
|
||||
tuple.Item2 = (ParameterAccessFlags)reader.ReadUInt16();
|
||||
}
|
||||
|
||||
return new MethodParameters(args);
|
||||
}
|
||||
|
||||
case "Code": return Code.Read(parent, reader);
|
||||
case "StackMapTable": return StackMapTable.Read(parent, reader);
|
||||
case "LocalVariableTable": return LocalVariableTable.Read(parent, reader);
|
||||
|
||||
default:
|
||||
{
|
||||
var data = new byte[length];
|
||||
reader.Stream.ReadExactly(data);
|
||||
return new GenericAttribute(attrName, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GenericAttribute(string name, ReadOnlyMemory<byte> data) : Attribute
|
||||
{
|
||||
public readonly ReadOnlyMemory<byte> Data = data;
|
||||
public readonly string Name = name;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ParameterAccessFlags : ushort
|
||||
{
|
||||
// Indicates that the formal parameter was declared final.
|
||||
Final = 0x0010,
|
||||
|
||||
// Indicates that the formal parameter was not explicitly or implicitly declared in source code, according to the specification of the language in which the source code was written (JLS §13.1). (The formal parameter is an implementation artifact of the compiler which produced this class file.)
|
||||
Synthetic = 0x1000,
|
||||
|
||||
// Indicates that the formal parameter was implicitly declared in source code, according to the specification of the language in which the source code was written (JLS §13.1). (The formal parameter is mandated by a language specification, so all compilers for the language must emit it.)
|
||||
Mandated = 0x8000,
|
||||
}
|
||||
|
||||
public sealed class MethodParameters(IReadOnlyList<(string, ParameterAccessFlags)> parameters) : Attribute
|
||||
{
|
||||
public readonly IReadOnlyList<(string, ParameterAccessFlags)> Parameters = parameters;
|
||||
}
|
||||
|
||||
public sealed class Code : Attribute
|
||||
{
|
||||
public ushort MaxStack { get; private init; }
|
||||
public ushort MaxLocals { get; private init; }
|
||||
public ReadOnlyMemory<byte> Bytecode { get; private init; }
|
||||
public IReadOnlyList<ExceptionTableEntry> ExceptionTable { get; private init; } = [];
|
||||
public IReadOnlyList<Attribute> Attributes { get; private init; } = [];
|
||||
|
||||
internal new static Code Read(Class parent, Reader reader)
|
||||
{
|
||||
var maxStack = reader.ReadUInt16();
|
||||
var maxLocals = reader.ReadUInt16();
|
||||
var codeLength = reader.ReadUInt32();
|
||||
|
||||
var code = new byte[codeLength];
|
||||
reader.Stream.ReadExactly(code);
|
||||
|
||||
var exceptions = new ExceptionTableEntry[reader.ReadUInt16()];
|
||||
foreach (ref var entry in exceptions.AsSpan())
|
||||
{
|
||||
entry.Start = reader.ReadUInt16();
|
||||
entry.End = reader.ReadUInt16();
|
||||
entry.Handler = reader.ReadUInt16();
|
||||
entry.CatchType = (Class)parent.GetConstant(reader.ReadUInt16())!;
|
||||
}
|
||||
|
||||
var attributes = new Attribute[reader.ReadUInt16()];
|
||||
foreach (ref var entry in attributes.AsSpan())
|
||||
entry = Attribute.Read(parent, reader);
|
||||
|
||||
return new Code
|
||||
{
|
||||
MaxStack = maxStack,
|
||||
MaxLocals = maxLocals,
|
||||
Bytecode = code,
|
||||
ExceptionTable = exceptions,
|
||||
Attributes = attributes,
|
||||
};
|
||||
}
|
||||
|
||||
public record struct ExceptionTableEntry(ushort Start, ushort End, ushort Handler, Class CatchType);
|
||||
}
|
||||
|
||||
public sealed class LocalVariableTable : Attribute
|
||||
{
|
||||
public IReadOnlyList<VariableEntry> Variables { get; private init; } = [];
|
||||
|
||||
internal new static LocalVariableTable Read(Class parent, Reader reader)
|
||||
{
|
||||
var variables = new VariableEntry[reader.ReadUInt16()];
|
||||
foreach (ref var variable in variables.AsSpan())
|
||||
{
|
||||
variable.Start = reader.ReadUInt16();
|
||||
variable.Length = reader.ReadUInt16();
|
||||
variable.Name = (string)parent.GetConstant(reader.ReadUInt16())!;
|
||||
variable.Signature = (string)parent.GetConstant(reader.ReadUInt16())!;
|
||||
variable.Index = reader.ReadUInt16();
|
||||
}
|
||||
|
||||
return new LocalVariableTable { Variables = variables };
|
||||
}
|
||||
|
||||
public record struct VariableEntry(ushort Start, ushort Length, string Name, string Signature, ushort Index);
|
||||
}
|
||||
|
||||
public sealed class StackMapTable : Attribute
|
||||
{
|
||||
public IReadOnlyList<Frame> Frames { get; private init; } = [];
|
||||
|
||||
public enum VerificationTypeInfoTag : byte
|
||||
{
|
||||
Top = 0,
|
||||
Integer = 1,
|
||||
Float = 2,
|
||||
Double = 3,
|
||||
Long = 4,
|
||||
Null = 5,
|
||||
UninitializedThis = 6,
|
||||
Object = 7,
|
||||
Uninitialized = 8,
|
||||
}
|
||||
|
||||
internal new static StackMapTable Read(Class parent, Reader reader)
|
||||
{
|
||||
var one = 0;
|
||||
var offset = 0;
|
||||
var frames = new Frame[reader.ReadUInt16()];
|
||||
foreach (ref var frame in frames.AsSpan())
|
||||
{
|
||||
var delta = 0;
|
||||
var frameType = reader.ReadUInt8();
|
||||
switch (frameType)
|
||||
{
|
||||
case < 64:
|
||||
delta = frameType + one;
|
||||
offset += delta;
|
||||
frame = new SameFrame(offset);
|
||||
break;
|
||||
|
||||
case < 128:
|
||||
delta = frameType - 64 + one;
|
||||
offset += delta;
|
||||
frame = new OneLocalFrame(offset, VerificationTypeInfo.Read(reader));
|
||||
break;
|
||||
|
||||
case 247:
|
||||
delta = reader.ReadUInt16() + one;
|
||||
offset += delta;
|
||||
frame = new OneLocalFrameExtended(offset, VerificationTypeInfo.Read(reader));
|
||||
break;
|
||||
|
||||
case 248 or 249 or 250:
|
||||
delta = reader.ReadUInt16() + one;
|
||||
offset += delta;
|
||||
frame = new ChopFrame(offset, (ushort)(251 - frameType));
|
||||
break;
|
||||
|
||||
case 251:
|
||||
delta = reader.ReadUInt16() + one;
|
||||
offset += delta;
|
||||
frame = new SameFrameExtended(offset);
|
||||
break;
|
||||
|
||||
case 252 or 253 or 254:
|
||||
delta = reader.ReadUInt16() + one;
|
||||
offset += delta;
|
||||
frame = new AppendFrame(offset, VerificationTypeInfo.Read(reader, frameType - 251));
|
||||
break;
|
||||
|
||||
case 255:
|
||||
delta = reader.ReadUInt16() + one;
|
||||
offset += delta;
|
||||
frame = new FullFrame(
|
||||
offset,
|
||||
VerificationTypeInfo.Read(reader, reader.ReadUInt16()),
|
||||
VerificationTypeInfo.Read(reader, reader.ReadUInt16())
|
||||
);
|
||||
break;
|
||||
|
||||
default: throw new NotImplementedException($"Unsupported frame type {frameType}");
|
||||
}
|
||||
one = 1;
|
||||
}
|
||||
|
||||
return new StackMapTable { Frames = frames };
|
||||
}
|
||||
|
||||
public struct VerificationTypeInfo
|
||||
{
|
||||
public VerificationTypeInfoTag Tag;
|
||||
public ushort Parameter;
|
||||
|
||||
public static VerificationTypeInfo Read(Reader reader)
|
||||
{
|
||||
var info = new VerificationTypeInfo { Tag = (VerificationTypeInfoTag)reader.ReadUInt8() };
|
||||
if (info.Tag is VerificationTypeInfoTag.Uninitialized or VerificationTypeInfoTag.Object)
|
||||
info.Parameter = reader.ReadUInt16();
|
||||
return info;
|
||||
}
|
||||
|
||||
public static VerificationTypeInfo[] Read(Reader reader, int count)
|
||||
{
|
||||
var infos = new VerificationTypeInfo[count];
|
||||
foreach (ref var info in infos.AsSpan()) info = Read(reader);
|
||||
return infos;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract record Frame(int StartOffset);
|
||||
|
||||
public sealed record ChopFrame(int StartOffset, ushort Count) : Frame(StartOffset);
|
||||
|
||||
public sealed record SameFrame(int StartOffset) : Frame(StartOffset);
|
||||
|
||||
public sealed record SameFrameExtended(int StartOffset) : Frame(StartOffset);
|
||||
|
||||
public sealed record OneLocalFrame(int StartOffset, VerificationTypeInfo Local) : Frame(StartOffset);
|
||||
|
||||
public sealed record OneLocalFrameExtended(int StartOffset, VerificationTypeInfo Local) : Frame(StartOffset);
|
||||
|
||||
public sealed record AppendFrame(int StartOffset, VerificationTypeInfo[] Locals) : Frame(StartOffset);
|
||||
|
||||
public sealed record FullFrame(
|
||||
int StartOffset,
|
||||
VerificationTypeInfo[] Locals,
|
||||
VerificationTypeInfo[] StackItems
|
||||
) : Frame(StartOffset);
|
||||
}
|
||||
Executable
+278
@@ -0,0 +1,278 @@
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace JCIL.Java.Class;
|
||||
|
||||
public record struct Version(ushort Minor, ushort Major);
|
||||
|
||||
[Flags]
|
||||
public enum ClassAccessFlags : ushort
|
||||
{
|
||||
// Declared public; may be accessed from outside its package.
|
||||
Public = 0x0001,
|
||||
|
||||
// Declared final; no subclasses allowed.
|
||||
Final = 0x0010,
|
||||
|
||||
// Treat superclass methods specially when invoked by the invokespecial instruction.
|
||||
Super = 0x0020,
|
||||
|
||||
// Is an interface, not a class.
|
||||
Interface = 0x0200,
|
||||
|
||||
// Declared abstract; must not be instantiated.
|
||||
Abstract = 0x0400,
|
||||
|
||||
// Declared synthetic; not present in the source code.
|
||||
Synthetic = 0x1000,
|
||||
|
||||
// Declared as an annotation type.
|
||||
Annotation = 0x2000,
|
||||
|
||||
// Declared as an enum type.
|
||||
Enum = 0x4000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FieldAccessFlags : ushort
|
||||
{
|
||||
// Declared public; may be accessed from outside its package.
|
||||
Public = 0x0001,
|
||||
|
||||
// Declared private; accessible only within the defining class and other classes belonging to the same nest (§5.4.4).
|
||||
Private = 0x0002,
|
||||
|
||||
// Declared protected; may be accessed within subclasses.
|
||||
Protected = 0x0004,
|
||||
|
||||
// Declared static.
|
||||
Static = 0x0008,
|
||||
|
||||
// Declared final; never directly assigned to after object construction (JLS §17.5).
|
||||
Final = 0x0010,
|
||||
|
||||
// Declared volatile; cannot be cached.
|
||||
Volatile = 0x0040,
|
||||
|
||||
// Declared transient; not written or read by a persistent object manager.
|
||||
Transient = 0x0080,
|
||||
|
||||
// Declared synthetic; not present in the source code.
|
||||
Synthetic = 0x1000,
|
||||
|
||||
// Declared as an element of an enum class.
|
||||
Enum = 0x4000,
|
||||
}
|
||||
|
||||
public enum IntrinsicType : byte
|
||||
{
|
||||
Void,
|
||||
Boolean,
|
||||
Byte,
|
||||
Char,
|
||||
Short,
|
||||
Int,
|
||||
Long,
|
||||
Float,
|
||||
Double,
|
||||
}
|
||||
|
||||
public sealed class Field
|
||||
{
|
||||
public readonly FieldAccessFlags AccessFlags;
|
||||
public readonly string Name;
|
||||
public readonly Class Type;
|
||||
public readonly IReadOnlyList<Attribute> Attributes;
|
||||
|
||||
private Field(FieldAccessFlags flags, string name, Class type, Attribute[] attributes)
|
||||
{
|
||||
AccessFlags = flags;
|
||||
Name = name;
|
||||
Type = type;
|
||||
Attributes = attributes;
|
||||
}
|
||||
|
||||
public static Field Read(Class parent, Reader reader)
|
||||
{
|
||||
var flags = (FieldAccessFlags)reader.ReadUInt16();
|
||||
var nameIndex = reader.ReadUInt16();
|
||||
var descriptorIndex = reader.ReadUInt16();
|
||||
var attributesCount = reader.ReadUInt16();
|
||||
|
||||
var name = (string) parent.GetConstant(nameIndex)!;
|
||||
var descriptor = (string) parent.GetConstant(descriptorIndex)!;
|
||||
var descriptorClass = parent.Loader.GetClass(descriptor.AsMemory());
|
||||
|
||||
var attributes = new Attribute[attributesCount];
|
||||
foreach (ref var attribute in attributes.AsSpan())
|
||||
attribute = Attribute.Read(parent, reader);
|
||||
return new Field(flags, name, descriptorClass, attributes);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ISpecialClassMetadata;
|
||||
|
||||
public sealed record ArrayMetadata(Class ElementClass) : ISpecialClassMetadata;
|
||||
|
||||
public sealed record IntrinsicMetadata(IntrinsicType Type) : ISpecialClassMetadata;
|
||||
|
||||
public sealed class Class
|
||||
{
|
||||
public Version Version { get; internal set; }
|
||||
public ClassAccessFlags AccessFlags { get; internal set; }
|
||||
public IReadOnlySet<Class> Interfaces { get; internal set; } = FrozenSet<Class>.Empty;
|
||||
public IReadOnlyList<Field> Fields { get; internal set; } = [];
|
||||
public IReadOnlyList<Method> Methods { get; internal set; } = [];
|
||||
public IReadOnlyList<Attribute> Attributes { get; internal set; } = [];
|
||||
public ISpecialClassMetadata? SpecialClassMetadata { get; internal set; }
|
||||
|
||||
public ReadOnlyMemory<char> Name { get; internal set; }
|
||||
public ReadOnlyMemory<char> Namespace { get; internal set; }
|
||||
|
||||
public Class? SuperClass
|
||||
{
|
||||
get;
|
||||
internal set
|
||||
{
|
||||
if (ReferenceEquals(value, this)) throw new InvalidOperationException("Super class cannot be self.");
|
||||
field = value;
|
||||
}
|
||||
} = null;
|
||||
|
||||
private Constant[] _constants = [];
|
||||
public int ConstantCount => _constants.Length;
|
||||
|
||||
|
||||
public readonly TypeLoader Loader;
|
||||
|
||||
internal Class(TypeLoader loader) => Loader = loader;
|
||||
|
||||
public object? GetConstant(ushort i)
|
||||
{
|
||||
return Constant.Resolve(Loader, _constants, i);
|
||||
}
|
||||
|
||||
public Method? GetMethod(string name, MethodSignature signature, bool exact = false)
|
||||
{
|
||||
if (exact)
|
||||
{
|
||||
foreach (var method in Methods)
|
||||
if (method.Name == name && ReferenceEquals(method.Type.SpecialClassMetadata, signature))
|
||||
return method;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetMethod(name, signature, true) is {} exactMethod)
|
||||
return exactMethod;
|
||||
|
||||
foreach (var method in Methods)
|
||||
if (method.Name == name && method.IsCompatibleWith(signature))
|
||||
return method;
|
||||
}
|
||||
|
||||
foreach (var interfaceClass in Interfaces)
|
||||
if (interfaceClass.GetMethod(name, signature, exact) is {} method)
|
||||
return method;
|
||||
|
||||
if(SuperClass is not null && SuperClass != this)
|
||||
return SuperClass.GetMethod(name, signature, exact);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Field? GetField(string name)
|
||||
{
|
||||
if (Fields.FirstOrDefault(f => f.Name == name) is { } field)
|
||||
return field;
|
||||
|
||||
if(SuperClass is not null && SuperClass != this)
|
||||
return SuperClass.GetField(name);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsAssignableTo(Class other)
|
||||
{
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
if (SuperClass is {} super && super.IsAssignableTo(other))
|
||||
return true;
|
||||
|
||||
if (SpecialClassMetadata is IntrinsicMetadata && other == Loader.Object)
|
||||
return true;
|
||||
|
||||
return Interfaces.Any(i => i.IsAssignableTo(other));
|
||||
}
|
||||
|
||||
public void Read(Stream stream)
|
||||
{
|
||||
var reader = new Reader(stream);
|
||||
var magic = reader.ReadUInt32();
|
||||
if (magic != 0xCAFEBABE)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Stream does not contain a valid Java class. Expected 0xCAFEBABE, found 0x{magic:X}");
|
||||
}
|
||||
|
||||
Version = new Version(reader.ReadUInt16(), reader.ReadUInt16());
|
||||
|
||||
var constantPoolCount = reader.ReadUInt16() - 1;
|
||||
var constants = new Constant[constantPoolCount];
|
||||
_constants = constants;
|
||||
for (var i = 0; i < constantPoolCount; i++)
|
||||
{
|
||||
var constant = Constant.Read(this, Loader, reader);
|
||||
constants[i] = constant;
|
||||
if (constant.Type is ConstantType.Long or ConstantType.Double)
|
||||
constants[i++] = constant;
|
||||
}
|
||||
|
||||
AccessFlags = (ClassAccessFlags)reader.ReadUInt16();
|
||||
var thisClass = (string) Constant.Resolve(Loader, constants, (ushort) constants[reader.ReadUInt16() - 1].Value)!;
|
||||
var nsSplit = thisClass.LastIndexOf('/');
|
||||
if (nsSplit != -1)
|
||||
{
|
||||
Namespace = thisClass.AsMemory()[..nsSplit];
|
||||
Name = thisClass.AsMemory()[(nsSplit+1)..];
|
||||
}
|
||||
else
|
||||
{
|
||||
Name = thisClass.AsMemory();
|
||||
}
|
||||
|
||||
SuperClass = Constant.Resolve(Loader, constants, reader.ReadUInt16()) as Class ?? (ReferenceEquals(this, Loader.Object) ? null : Loader.Object);
|
||||
|
||||
var interfacesCount = reader.ReadUInt16();
|
||||
var interfaces = new HashSet<Class>(interfacesCount);
|
||||
Interfaces = interfaces;
|
||||
for (var i = 0; i < interfacesCount; i++)
|
||||
interfaces.Add((Class)GetConstant(reader.ReadUInt16())!);
|
||||
|
||||
var fieldCount = reader.ReadUInt16();
|
||||
var fields = new Field[fieldCount];
|
||||
Fields = fields;
|
||||
foreach (ref var field in fields.AsSpan())
|
||||
field = Field.Read(this, reader);
|
||||
|
||||
var methodCount = reader.ReadUInt16();
|
||||
var methods = new Method[methodCount];
|
||||
Methods = methods;
|
||||
foreach (ref var method in methods.AsSpan())
|
||||
method = Method.Read(this, reader);
|
||||
|
||||
var attributeCount = reader.ReadUInt16();
|
||||
var attributes = new Attribute[attributeCount];
|
||||
Attributes = attributes;
|
||||
foreach (ref var attribute in attributes.AsSpan())
|
||||
attribute = Attribute.Read(this, reader);
|
||||
}
|
||||
|
||||
public Class WithGenerics(IReadOnlyList<Class> generics)
|
||||
{
|
||||
var other = (Class) MemberwiseClone();
|
||||
other.SuperClass = this;
|
||||
return other;
|
||||
}
|
||||
|
||||
public override string ToString() => Namespace.IsEmpty ? Name.ToString() : $"{Namespace}/{Name}";
|
||||
}
|
||||
Executable
+158
@@ -0,0 +1,158 @@
|
||||
using System.Text;
|
||||
|
||||
namespace JCIL.Java.Class;
|
||||
|
||||
public enum ConstantType
|
||||
{
|
||||
Class = 7,
|
||||
FieldRef = 9,
|
||||
MethodRef = 10,
|
||||
InterfaceMethodRef = 11,
|
||||
String = 8,
|
||||
Integer = 3,
|
||||
Float = 4,
|
||||
Long = 5,
|
||||
Double = 6,
|
||||
NameAndType = 12,
|
||||
Utf8 = 1,
|
||||
MethodHandle = 15,
|
||||
MethodType = 16,
|
||||
InvokeDynamic = 18,
|
||||
}
|
||||
|
||||
public sealed record NameAndType(string Name, string Descriptor);
|
||||
|
||||
public sealed record MethodRef(Class Class, NameAndType Method);
|
||||
public sealed record FieldRef(Class Class, NameAndType Field);
|
||||
|
||||
public record struct Constant(ConstantType Type, object Value)
|
||||
{
|
||||
public static Constant Read(Class self, TypeLoader loader, Reader reader)
|
||||
{
|
||||
object value;
|
||||
var type = (ConstantType)reader.ReadUInt8();
|
||||
switch (type)
|
||||
{
|
||||
|
||||
case ConstantType.Class:
|
||||
case ConstantType.String:
|
||||
case ConstantType.MethodType:
|
||||
{
|
||||
value = reader.ReadUInt16();
|
||||
break;
|
||||
}
|
||||
|
||||
case ConstantType.Integer:
|
||||
{
|
||||
value = reader.ReadInt32();
|
||||
break;
|
||||
}
|
||||
|
||||
case ConstantType.Float:
|
||||
{
|
||||
value = reader.ReadFloat();
|
||||
break;
|
||||
}
|
||||
|
||||
case ConstantType.Long:
|
||||
{
|
||||
value = reader.ReadInt64();
|
||||
break;
|
||||
}
|
||||
|
||||
case ConstantType.Double:
|
||||
{
|
||||
value = reader.ReadDouble();
|
||||
break;
|
||||
}
|
||||
|
||||
case ConstantType.FieldRef:
|
||||
case ConstantType.MethodRef:
|
||||
case ConstantType.NameAndType:
|
||||
case ConstantType.InvokeDynamic:
|
||||
case ConstantType.InterfaceMethodRef:
|
||||
{
|
||||
value = (reader.ReadUInt16(), reader.ReadUInt16());
|
||||
break;
|
||||
}
|
||||
|
||||
case ConstantType.Utf8:
|
||||
{
|
||||
var length = reader.ReadUInt16();
|
||||
var buffer = new byte[length];
|
||||
reader.Stream.ReadExactly(buffer);
|
||||
value = Encoding.UTF8.GetString(buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case ConstantType.MethodHandle:
|
||||
{
|
||||
value = (reader.ReadUInt8(), reader.ReadUInt16());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
value = null!;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new Constant(type, value);
|
||||
}
|
||||
|
||||
public static object? Resolve(TypeLoader loader, Constant[] constants, ushort i)
|
||||
{
|
||||
if (i == 0 || i > constants.Length) return null;
|
||||
i -= 1;
|
||||
switch (constants[i])
|
||||
{
|
||||
case { Type: ConstantType.Class, Value: ushort index }:
|
||||
{
|
||||
var name = (string) Resolve(loader, constants, index)!;
|
||||
constants[i] = new Constant(ConstantType.Class, loader.GetClass(name.AsMemory()));
|
||||
break;
|
||||
}
|
||||
|
||||
case { Type: ConstantType.MethodRef, Value: (ushort classIndex, ushort nameTypeIndex) }:
|
||||
{
|
||||
var name = (Class)Resolve(loader, constants, classIndex)!;
|
||||
var descriptor = (NameAndType)Resolve(loader, constants, nameTypeIndex)!;
|
||||
constants[i] = new Constant(ConstantType.MethodRef, new MethodRef(name, descriptor));
|
||||
break;
|
||||
}
|
||||
|
||||
case { Type: ConstantType.FieldRef, Value: (ushort classIndex, ushort nameTypeIndex) }:
|
||||
{
|
||||
var name = (Class)Resolve(loader, constants, classIndex)!;
|
||||
var descriptor = (NameAndType)Resolve(loader, constants, nameTypeIndex)!;
|
||||
constants[i] = new Constant(ConstantType.FieldRef, new FieldRef(name, descriptor));
|
||||
break;
|
||||
}
|
||||
|
||||
case { Type: ConstantType.InterfaceMethodRef, Value: (ushort classIndex, ushort nameTypeIndex) }:
|
||||
{
|
||||
var name = (Class)Resolve(loader, constants, classIndex)!;
|
||||
var descriptor = (NameAndType)Resolve(loader, constants, nameTypeIndex)!;
|
||||
constants[i] = new Constant(ConstantType.InterfaceMethodRef, new MethodRef(name, descriptor));
|
||||
break;
|
||||
}
|
||||
|
||||
case { Type: ConstantType.String, Value: ushort index }:
|
||||
{
|
||||
var text = (string)Resolve(loader, constants, index)!;
|
||||
constants[i] = new Constant(ConstantType.String, text);
|
||||
break;
|
||||
}
|
||||
|
||||
case { Type: ConstantType.NameAndType, Value: (ushort nameIndex, ushort descriptorIndex) }:
|
||||
{
|
||||
var name = (string)Resolve(loader, constants, nameIndex)!;
|
||||
var descriptor = (string)Resolve(loader, constants, descriptorIndex)!;
|
||||
constants[i] = new Constant(ConstantType.NameAndType, new NameAndType(name, descriptor));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return constants[i].Value;
|
||||
}
|
||||
}
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>JCIL.Java.Class</AssemblyName>
|
||||
<RootNamespace>JCIL.Java.Class</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\JCIL.Core\JCIL.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Executable
+613
@@ -0,0 +1,613 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace JCIL.Java.Class;
|
||||
|
||||
[Flags]
|
||||
public enum MethodAccessFlags : ushort
|
||||
{
|
||||
// Declared public; may be accessed from outside its package.
|
||||
Public = 0x0001,
|
||||
|
||||
// Declared private; accessible only within the defining class and other classes belonging to the same nest (§5.4.4).
|
||||
Private = 0x0002,
|
||||
|
||||
// Declared protected; may be accessed within subclasses.
|
||||
Protected = 0x0004,
|
||||
|
||||
// Declared static.
|
||||
Static = 0x0008,
|
||||
|
||||
// Declared final; must not be overridden (§5.4.5).
|
||||
Final = 0x0010,
|
||||
|
||||
// Declared synchronized; invocation is wrapped by a monitor use.
|
||||
Synchronized = 0x0020,
|
||||
|
||||
// A bridge method, generated by the compiler.
|
||||
Bridge = 0x0040,
|
||||
|
||||
// Declared with variable number of arguments.
|
||||
Varargs = 0x0080,
|
||||
|
||||
// Declared native; implemented in a language other than the Java programming language.
|
||||
Native = 0x0100,
|
||||
|
||||
// Declared abstract; no implementation is provided.
|
||||
Abstract = 0x0400,
|
||||
|
||||
// In a class file whose major version number is at least 46 and at most 60: Declared strictfp.
|
||||
Strict = 0x0800,
|
||||
|
||||
// Declared synthetic; not present in the source code.
|
||||
Synthetic = 0x1000,
|
||||
}
|
||||
|
||||
public sealed record MethodSignature(Class ReturnType, IReadOnlyList<Class> ParamTypes) : ISpecialClassMetadata;
|
||||
|
||||
public sealed class Method
|
||||
{
|
||||
public readonly MethodAccessFlags AccessFlags;
|
||||
public readonly string Name;
|
||||
public readonly Class Type;
|
||||
public readonly IReadOnlyList<Attribute> Attributes;
|
||||
|
||||
private Method(MethodAccessFlags flags, string name, Class type, Attribute[] attributes)
|
||||
{
|
||||
AccessFlags = flags;
|
||||
Name = name;
|
||||
Type = type;
|
||||
Attributes = attributes;
|
||||
}
|
||||
|
||||
public static Method Read(Class parent, Reader reader)
|
||||
{
|
||||
var flags = (MethodAccessFlags)reader.ReadUInt16();
|
||||
var nameIndex = reader.ReadUInt16();
|
||||
var descriptorIndex = reader.ReadUInt16();
|
||||
var attributesCount = reader.ReadUInt16();
|
||||
|
||||
var name = (string)parent.GetConstant(nameIndex)!;
|
||||
var descriptor = (string)parent.GetConstant(descriptorIndex)!;
|
||||
var descriptorClass = parent.Loader.GetClass(descriptor.AsMemory());
|
||||
|
||||
var attributes = new Attribute[attributesCount];
|
||||
foreach (ref var attribute in attributes.AsSpan())
|
||||
attribute = Attribute.Read(parent, reader);
|
||||
return new Method(flags, name, descriptorClass, attributes);
|
||||
}
|
||||
|
||||
public bool TryGetCode([MaybeNullWhen(false)] out Code code, out OpCodeReader opcodes)
|
||||
{
|
||||
if (Attributes.OfType<Code>().FirstOrDefault() is { } attr)
|
||||
{
|
||||
MemoryMarshal.TryGetArray(attr.Bytecode, out var array);
|
||||
code = attr;
|
||||
opcodes = new OpCodeReader(new MemoryStream(array.Array!));
|
||||
return true;
|
||||
}
|
||||
|
||||
code = null!;
|
||||
opcodes = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsCompatibleWith(MethodSignature signature)
|
||||
{
|
||||
var methodSig = (MethodSignature)Type.SpecialClassMetadata!;
|
||||
|
||||
if (!signature.ReturnType.IsAssignableTo(methodSig.ReturnType))
|
||||
return false;
|
||||
|
||||
if ((AccessFlags & MethodAccessFlags.Varargs) == 0 && methodSig.ParamTypes.Count != signature.ParamTypes.Count)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < signature.ParamTypes.Count; i++)
|
||||
{
|
||||
var current = methodSig.ParamTypes[Math.Clamp(i, 0, methodSig.ParamTypes.Count - 1)];
|
||||
if (i >= methodSig.ParamTypes.Count - 1) current = ((ArrayMetadata)current.SpecialClassMetadata!).ElementClass;
|
||||
if (!signature.ParamTypes[i].IsAssignableTo(current)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var signature = (MethodSignature)Type.SpecialClassMetadata!;
|
||||
return $"{signature.ReturnType} {Name}({string.Join(", ", signature.ParamTypes)})";
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public enum OpCodeMnemonic : byte
|
||||
{
|
||||
Nop = 0,
|
||||
AConstNull = 1,
|
||||
IConstM1 = 2,
|
||||
IConst0 = 3,
|
||||
IConst1 = 4,
|
||||
IConst2 = 5,
|
||||
IConst3 = 6,
|
||||
IConst4 = 7,
|
||||
IConst5 = 8,
|
||||
LConst0 = 9,
|
||||
LConst1 = 10,
|
||||
FConst0 = 11,
|
||||
FConst1 = 12,
|
||||
FConst2 = 13,
|
||||
DConst0 = 14,
|
||||
DConst1 = 15,
|
||||
PushByte = 16,
|
||||
PushShort = 17,
|
||||
Ldc = 18,
|
||||
LdcW = 19,
|
||||
Ldc2W = 20,
|
||||
ILoad = 21,
|
||||
LLoad = 22,
|
||||
FLoad = 23,
|
||||
DLoad = 24,
|
||||
ALoad = 25,
|
||||
ILoad0 = 26,
|
||||
ILoad1 = 27,
|
||||
ILoad2 = 28,
|
||||
ILoad3 = 29,
|
||||
LLoad0 = 30,
|
||||
LLoad1 = 31,
|
||||
LLoad2 = 32,
|
||||
LLoad3 = 33,
|
||||
FLoad0 = 34,
|
||||
FLoad1 = 35,
|
||||
FLoad2 = 36,
|
||||
FLoad3 = 37,
|
||||
DLoad0 = 38,
|
||||
DLoad1 = 39,
|
||||
DLoad2 = 40,
|
||||
DLoad3 = 41,
|
||||
ALoad0 = 42,
|
||||
ALoad1 = 43,
|
||||
ALoad2 = 44,
|
||||
ALoad3 = 45,
|
||||
IALoad = 46,
|
||||
LALoad = 47,
|
||||
AALoad = 50,
|
||||
BALoad = 51,
|
||||
CALoad = 52,
|
||||
SALoad = 53,
|
||||
IStore = 54,
|
||||
LStore = 55,
|
||||
FStore = 56,
|
||||
DStore = 57,
|
||||
AStore = 58,
|
||||
IStore0 = 59,
|
||||
IStore1 = 60,
|
||||
IStore2 = 61,
|
||||
IStore3 = 62,
|
||||
LStore0 = 63,
|
||||
LStore1 = 64,
|
||||
LStore2 = 65,
|
||||
LStore3 = 66,
|
||||
AStore0 = 75,
|
||||
AStore1 = 76,
|
||||
AStore2 = 77,
|
||||
IAStore = 79,
|
||||
AStore3 = 78,
|
||||
LAStore = 80,
|
||||
AAStore = 83,
|
||||
BAStore = 84,
|
||||
CAStore = 85,
|
||||
Pop = 87,
|
||||
Pop2 = 88,
|
||||
Dup = 89,
|
||||
DupX1 = 90,
|
||||
DupX2 = 91,
|
||||
Dup2 = 92,
|
||||
IAdd = 96,
|
||||
LAdd = 97,
|
||||
FAdd = 98,
|
||||
DAdd = 99,
|
||||
ISub = 100,
|
||||
LSub = 101,
|
||||
FSub = 102,
|
||||
DSub = 103,
|
||||
IMul = 104,
|
||||
LMul = 105,
|
||||
FMul = 106,
|
||||
DMul = 107,
|
||||
IDiv = 108,
|
||||
LDiv = 109,
|
||||
FDiv = 110,
|
||||
DDiv = 111,
|
||||
IRem = 112,
|
||||
LRem = 113,
|
||||
FRem = 114,
|
||||
DRem = 115,
|
||||
INeg = 116,
|
||||
LNeg = 117,
|
||||
IShl = 120,
|
||||
LShl = 121,
|
||||
IShr = 122,
|
||||
LShr = 123,
|
||||
IUShr = 124,
|
||||
LUShr = 125,
|
||||
IAnd = 126,
|
||||
LAnd = 127,
|
||||
IOr = 128,
|
||||
LOr = 129,
|
||||
IXor = 130,
|
||||
LXor = 131,
|
||||
IInc = 132,
|
||||
IntToLong = 133,
|
||||
IntToFloat = 134,
|
||||
IntToDouble = 135,
|
||||
LongToInt = 136,
|
||||
LongToFloat = 137,
|
||||
LongToDouble = 138,
|
||||
FloatToInt = 139,
|
||||
FloatToLong = 140,
|
||||
FloatToDouble = 141,
|
||||
DoubleToInt = 142,
|
||||
DoubleToLong = 143,
|
||||
DoubleToFloat = 144,
|
||||
IntToByte = 145,
|
||||
IntToChar = 146,
|
||||
IntToShort = 147,
|
||||
LCmp = 148,
|
||||
FCmpL = 149,
|
||||
FCmpG = 150,
|
||||
DCmpL = 151,
|
||||
DCmpG = 152,
|
||||
IfEq = 153,
|
||||
IfNe = 154,
|
||||
IfLt = 155,
|
||||
IfGe = 156,
|
||||
IfGt = 157,
|
||||
IfLe = 158,
|
||||
IfICmpEq = 159,
|
||||
IfICmpNe = 160,
|
||||
IfICmpLt = 161,
|
||||
IfICmpGe = 162,
|
||||
IfICmpGt = 163,
|
||||
IfICmpLe = 164,
|
||||
IfACmpEq = 165,
|
||||
IfACmpNe = 166,
|
||||
Goto = 167,
|
||||
TableSwitch = 170,
|
||||
LookupSwitch = 171,
|
||||
IReturn = 172,
|
||||
LReturn = 173,
|
||||
FReturn = 174,
|
||||
DReturn = 175,
|
||||
AReturn = 176,
|
||||
Return = 177,
|
||||
GetStaticField = 178,
|
||||
SetStaticField = 179,
|
||||
GetField = 180,
|
||||
SetField = 181,
|
||||
InvokeVirtual = 182,
|
||||
InvokeSpecial = 183,
|
||||
InvokeStatic = 184,
|
||||
InvokeInterface = 185,
|
||||
InvokeDynamic = 186,
|
||||
New = 187,
|
||||
NewArray = 188,
|
||||
ANewArray = 189,
|
||||
ArrayLen = 190,
|
||||
AThrow = 191,
|
||||
CheckCast = 192,
|
||||
InstanceOf = 193,
|
||||
MonitorEnter = 194,
|
||||
MonitorExit = 195,
|
||||
IfNull = 198,
|
||||
IfNonNull = 199,
|
||||
}
|
||||
|
||||
public struct OpCode
|
||||
{
|
||||
public OpCodeMnemonic Mnemonic;
|
||||
public OpCodeParameter P0;
|
||||
public OpCodeParameter P1;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var p0 = P0.Object is null ? P0.ULong.ToString() : P0.Object.ToString();
|
||||
var p1 = P1.Object is null ? P1.ULong.ToString() : P1.Object.ToString();
|
||||
return $"{Mnemonic} {p0}, {p1}";
|
||||
}
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct OpCodeParameter
|
||||
{
|
||||
[FieldOffset(0)] public byte Byte;
|
||||
[FieldOffset(0)] public ushort UShort;
|
||||
[FieldOffset(0)] public uint UInt;
|
||||
[FieldOffset(0)] public ulong ULong;
|
||||
[FieldOffset(0)] public float Float;
|
||||
[FieldOffset(0)] public double Double;
|
||||
[FieldOffset(8)] public object? Object;
|
||||
}
|
||||
|
||||
public struct OpCodeReader(Stream bytecode) : IEnumerator<OpCode>
|
||||
{
|
||||
private OpCode _current;
|
||||
public Reader Reader = new(bytecode);
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
var b = Reader.Stream.ReadByte();
|
||||
if (b == -1)
|
||||
{
|
||||
_current = default;
|
||||
return false;
|
||||
}
|
||||
_current = new OpCode { Mnemonic = (OpCodeMnemonic)(byte)b };
|
||||
switch (_current.Mnemonic)
|
||||
{
|
||||
case OpCodeMnemonic.PushByte:
|
||||
case OpCodeMnemonic.ILoad:
|
||||
case OpCodeMnemonic.LLoad:
|
||||
case OpCodeMnemonic.FLoad:
|
||||
case OpCodeMnemonic.DLoad:
|
||||
case OpCodeMnemonic.ALoad:
|
||||
case OpCodeMnemonic.IStore:
|
||||
case OpCodeMnemonic.AStore:
|
||||
case OpCodeMnemonic.LStore:
|
||||
case OpCodeMnemonic.FStore:
|
||||
case OpCodeMnemonic.DStore:
|
||||
case OpCodeMnemonic.NewArray:
|
||||
case OpCodeMnemonic.Ldc:
|
||||
{
|
||||
_current.P0.UShort = Reader.ReadUInt8();
|
||||
return true;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.IInc:
|
||||
{
|
||||
_current.P0.UShort = Reader.ReadUInt8();
|
||||
_current.P1.Byte = Reader.ReadUInt8();
|
||||
return true;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.PushShort:
|
||||
case OpCodeMnemonic.LdcW:
|
||||
case OpCodeMnemonic.Ldc2W:
|
||||
case OpCodeMnemonic.InvokeVirtual:
|
||||
case OpCodeMnemonic.InvokeSpecial:
|
||||
case OpCodeMnemonic.InvokeStatic:
|
||||
case OpCodeMnemonic.IfNull:
|
||||
case OpCodeMnemonic.IfNonNull:
|
||||
case OpCodeMnemonic.IfEq:
|
||||
case OpCodeMnemonic.IfNe:
|
||||
case OpCodeMnemonic.IfLt:
|
||||
case OpCodeMnemonic.IfLe:
|
||||
case OpCodeMnemonic.IfGe:
|
||||
case OpCodeMnemonic.IfGt:
|
||||
case OpCodeMnemonic.IfICmpEq:
|
||||
case OpCodeMnemonic.IfICmpNe:
|
||||
case OpCodeMnemonic.IfICmpLt:
|
||||
case OpCodeMnemonic.IfICmpLe:
|
||||
case OpCodeMnemonic.IfICmpGe:
|
||||
case OpCodeMnemonic.IfICmpGt:
|
||||
case OpCodeMnemonic.IfACmpEq:
|
||||
case OpCodeMnemonic.IfACmpNe:
|
||||
case OpCodeMnemonic.Goto:
|
||||
case OpCodeMnemonic.New:
|
||||
case OpCodeMnemonic.GetField:
|
||||
case OpCodeMnemonic.SetField:
|
||||
case OpCodeMnemonic.GetStaticField:
|
||||
case OpCodeMnemonic.SetStaticField:
|
||||
case OpCodeMnemonic.CheckCast:
|
||||
case OpCodeMnemonic.InstanceOf:
|
||||
case OpCodeMnemonic.ANewArray:
|
||||
{
|
||||
_current.P0.UShort = Reader.ReadUInt16();
|
||||
return true;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.InvokeInterface:
|
||||
case OpCodeMnemonic.InvokeDynamic:
|
||||
{
|
||||
_current.P0.UShort = Reader.ReadUInt16();
|
||||
_current.P1.UShort = Reader.ReadUInt16();
|
||||
return true;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.TableSwitch:
|
||||
{
|
||||
while (Reader.Stream.Position % 4 != 0)
|
||||
if (Reader.Stream.ReadByte() < 0)
|
||||
return false;
|
||||
|
||||
var table = new TableSwitch();
|
||||
_current.P0.Object = table;
|
||||
table.Default = Reader.ReadInt32();
|
||||
table.Low = Reader.ReadInt32();
|
||||
table.High = Reader.ReadInt32();
|
||||
table.Offsets = new int[table.High - table.Low + 1];
|
||||
foreach (ref var offset in table.Offsets.AsSpan())
|
||||
offset = Reader.ReadInt32();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.LookupSwitch:
|
||||
{
|
||||
while (Reader.Stream.Position % 4 != 0)
|
||||
if (Reader.Stream.ReadByte() < 0)
|
||||
return false;
|
||||
|
||||
var table = new LookupSwitch();
|
||||
_current.P0.Object = table;
|
||||
table.Default = Reader.ReadInt32();
|
||||
table.Entries = new KeyValuePair<int, int>[Reader.ReadUInt32()];
|
||||
foreach (ref var kv in table.Entries.AsSpan())
|
||||
kv = new KeyValuePair<int, int>(Reader.ReadInt32(), Reader.ReadInt32());
|
||||
|
||||
return true;
|
||||
}
|
||||
case OpCodeMnemonic.Nop:
|
||||
case OpCodeMnemonic.AConstNull:
|
||||
case OpCodeMnemonic.IConstM1:
|
||||
case OpCodeMnemonic.IConst0:
|
||||
case OpCodeMnemonic.IConst1:
|
||||
case OpCodeMnemonic.IConst2:
|
||||
case OpCodeMnemonic.IConst3:
|
||||
case OpCodeMnemonic.IConst4:
|
||||
case OpCodeMnemonic.IConst5:
|
||||
case OpCodeMnemonic.LConst0:
|
||||
case OpCodeMnemonic.LConst1:
|
||||
case OpCodeMnemonic.FConst0:
|
||||
case OpCodeMnemonic.FConst1:
|
||||
case OpCodeMnemonic.FConst2:
|
||||
case OpCodeMnemonic.DConst0:
|
||||
case OpCodeMnemonic.DConst1:
|
||||
case OpCodeMnemonic.ILoad0:
|
||||
case OpCodeMnemonic.ILoad1:
|
||||
case OpCodeMnemonic.ILoad2:
|
||||
case OpCodeMnemonic.ILoad3:
|
||||
case OpCodeMnemonic.LLoad0:
|
||||
case OpCodeMnemonic.LLoad1:
|
||||
case OpCodeMnemonic.LLoad2:
|
||||
case OpCodeMnemonic.LLoad3:
|
||||
case OpCodeMnemonic.FLoad0:
|
||||
case OpCodeMnemonic.FLoad1:
|
||||
case OpCodeMnemonic.FLoad2:
|
||||
case OpCodeMnemonic.FLoad3:
|
||||
case OpCodeMnemonic.DLoad0:
|
||||
case OpCodeMnemonic.DLoad1:
|
||||
case OpCodeMnemonic.DLoad2:
|
||||
case OpCodeMnemonic.DLoad3:
|
||||
case OpCodeMnemonic.ALoad0:
|
||||
case OpCodeMnemonic.ALoad1:
|
||||
case OpCodeMnemonic.ALoad2:
|
||||
case OpCodeMnemonic.ALoad3:
|
||||
case OpCodeMnemonic.IALoad:
|
||||
case OpCodeMnemonic.LALoad:
|
||||
case OpCodeMnemonic.AALoad:
|
||||
case OpCodeMnemonic.BALoad:
|
||||
case OpCodeMnemonic.CALoad:
|
||||
case OpCodeMnemonic.SALoad:
|
||||
case OpCodeMnemonic.IStore0:
|
||||
case OpCodeMnemonic.IStore1:
|
||||
case OpCodeMnemonic.IStore2:
|
||||
case OpCodeMnemonic.IStore3:
|
||||
case OpCodeMnemonic.LStore0:
|
||||
case OpCodeMnemonic.LStore1:
|
||||
case OpCodeMnemonic.LStore2:
|
||||
case OpCodeMnemonic.LStore3:
|
||||
case OpCodeMnemonic.AStore0:
|
||||
case OpCodeMnemonic.AStore1:
|
||||
case OpCodeMnemonic.AStore2:
|
||||
case OpCodeMnemonic.AStore3:
|
||||
case OpCodeMnemonic.LAStore:
|
||||
case OpCodeMnemonic.IAStore:
|
||||
case OpCodeMnemonic.AAStore:
|
||||
case OpCodeMnemonic.BAStore:
|
||||
case OpCodeMnemonic.CAStore:
|
||||
case OpCodeMnemonic.Pop:
|
||||
case OpCodeMnemonic.Pop2:
|
||||
case OpCodeMnemonic.Dup:
|
||||
case OpCodeMnemonic.DupX1:
|
||||
case OpCodeMnemonic.DupX2:
|
||||
case OpCodeMnemonic.Dup2:
|
||||
case OpCodeMnemonic.IAdd:
|
||||
case OpCodeMnemonic.LAdd:
|
||||
case OpCodeMnemonic.FAdd:
|
||||
case OpCodeMnemonic.DAdd:
|
||||
case OpCodeMnemonic.ISub:
|
||||
case OpCodeMnemonic.LSub:
|
||||
case OpCodeMnemonic.FSub:
|
||||
case OpCodeMnemonic.DSub:
|
||||
case OpCodeMnemonic.IMul:
|
||||
case OpCodeMnemonic.LMul:
|
||||
case OpCodeMnemonic.FMul:
|
||||
case OpCodeMnemonic.DMul:
|
||||
case OpCodeMnemonic.IDiv:
|
||||
case OpCodeMnemonic.LDiv:
|
||||
case OpCodeMnemonic.FDiv:
|
||||
case OpCodeMnemonic.DDiv:
|
||||
case OpCodeMnemonic.IRem:
|
||||
case OpCodeMnemonic.LRem:
|
||||
case OpCodeMnemonic.FRem:
|
||||
case OpCodeMnemonic.DRem:
|
||||
case OpCodeMnemonic.INeg:
|
||||
case OpCodeMnemonic.LNeg:
|
||||
case OpCodeMnemonic.IShl:
|
||||
case OpCodeMnemonic.LShl:
|
||||
case OpCodeMnemonic.IShr:
|
||||
case OpCodeMnemonic.LShr:
|
||||
case OpCodeMnemonic.IUShr:
|
||||
case OpCodeMnemonic.LUShr:
|
||||
case OpCodeMnemonic.IAnd:
|
||||
case OpCodeMnemonic.LAnd:
|
||||
case OpCodeMnemonic.IOr:
|
||||
case OpCodeMnemonic.LOr:
|
||||
case OpCodeMnemonic.IXor:
|
||||
case OpCodeMnemonic.LXor:
|
||||
case OpCodeMnemonic.IntToLong:
|
||||
case OpCodeMnemonic.IntToFloat:
|
||||
case OpCodeMnemonic.IntToDouble:
|
||||
case OpCodeMnemonic.IntToByte:
|
||||
case OpCodeMnemonic.IntToChar:
|
||||
case OpCodeMnemonic.IntToShort:
|
||||
case OpCodeMnemonic.LongToInt:
|
||||
case OpCodeMnemonic.LongToFloat:
|
||||
case OpCodeMnemonic.LongToDouble:
|
||||
case OpCodeMnemonic.FloatToInt:
|
||||
case OpCodeMnemonic.FloatToLong:
|
||||
case OpCodeMnemonic.FloatToDouble:
|
||||
case OpCodeMnemonic.DoubleToInt:
|
||||
case OpCodeMnemonic.DoubleToLong:
|
||||
case OpCodeMnemonic.DoubleToFloat:
|
||||
case OpCodeMnemonic.LCmp:
|
||||
case OpCodeMnemonic.FCmpG:
|
||||
case OpCodeMnemonic.FCmpL:
|
||||
case OpCodeMnemonic.DCmpG:
|
||||
case OpCodeMnemonic.DCmpL:
|
||||
case OpCodeMnemonic.IReturn:
|
||||
case OpCodeMnemonic.AReturn:
|
||||
case OpCodeMnemonic.LReturn:
|
||||
case OpCodeMnemonic.FReturn:
|
||||
case OpCodeMnemonic.DReturn:
|
||||
case OpCodeMnemonic.Return:
|
||||
case OpCodeMnemonic.ArrayLen:
|
||||
case OpCodeMnemonic.AThrow:
|
||||
case OpCodeMnemonic.MonitorEnter:
|
||||
case OpCodeMnemonic.MonitorExit:
|
||||
return true;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"{_current.Mnemonic} is not a valid opcode");
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Reader.Stream.Position = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public OpCode Current => _current;
|
||||
object IEnumerator.Current => _current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Reader.Stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TableSwitch
|
||||
{
|
||||
public int Default;
|
||||
public int Low;
|
||||
public int High;
|
||||
public int[] Offsets = [];
|
||||
}
|
||||
|
||||
public sealed class LookupSwitch
|
||||
{
|
||||
public int Default;
|
||||
public KeyValuePair<int, int>[] Entries = [];
|
||||
}
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace JCIL.Java.Class;
|
||||
|
||||
public struct Reader(Stream stream)
|
||||
{
|
||||
public readonly Stream Stream = stream;
|
||||
|
||||
public sbyte ReadInt8()
|
||||
{
|
||||
return (sbyte)Stream.ReadByte();
|
||||
}
|
||||
|
||||
public short ReadInt16()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[2];
|
||||
Stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadInt16BigEndian(buffer);
|
||||
}
|
||||
|
||||
public int ReadInt32()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
Stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadInt32BigEndian(buffer);
|
||||
}
|
||||
|
||||
public long ReadInt64()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[8];
|
||||
Stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadInt64BigEndian(buffer);
|
||||
}
|
||||
|
||||
public byte ReadUInt8()
|
||||
{
|
||||
return (byte)Stream.ReadByte();
|
||||
}
|
||||
|
||||
public ushort ReadUInt16()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[2];
|
||||
Stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadUInt16BigEndian(buffer);
|
||||
}
|
||||
|
||||
public uint ReadUInt32()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
Stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadUInt32BigEndian(buffer);
|
||||
}
|
||||
|
||||
public ulong ReadUInt64()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[8];
|
||||
Stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadUInt64BigEndian(buffer);
|
||||
}
|
||||
|
||||
public float ReadFloat()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
Stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadSingleBigEndian(buffer);
|
||||
}
|
||||
|
||||
public double ReadDouble()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[8];
|
||||
Stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadDoubleBigEndian(buffer);
|
||||
}
|
||||
}
|
||||
Executable
+253
@@ -0,0 +1,253 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace JCIL.Java.Class;
|
||||
|
||||
public sealed class TypeLoader
|
||||
{
|
||||
private readonly ConcurrentDictionary<ReadOnlyMemory<char>, string> _files =
|
||||
new(ReadOnlyMemoryCharComparer.Instance);
|
||||
|
||||
private readonly ConcurrentDictionary<ReadOnlyMemory<char>, Class> _classes =
|
||||
new(ReadOnlyMemoryCharComparer.Instance);
|
||||
|
||||
public Class Object
|
||||
{
|
||||
get
|
||||
{
|
||||
field ??= LoadClass("java/lang/Object".AsMemory());
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPath(string path)
|
||||
{
|
||||
var basePath = path.AsSpan().TrimEnd(Path.DirectorySeparatorChar);
|
||||
foreach (var file in Directory.EnumerateFiles(path, "*.class", SearchOption.AllDirectories))
|
||||
{
|
||||
var name = file[(basePath.Length + 1)..^6];
|
||||
_files.AddOrUpdate(name.AsMemory(), file, (_, v) => v);
|
||||
}
|
||||
}
|
||||
|
||||
public Class GetClass(ReadOnlyMemory<char> className)
|
||||
{
|
||||
return _classes.TryGetValue(className, out var existing) ? existing : GetClass(className, out _);
|
||||
}
|
||||
|
||||
public Class GetClass(ReadOnlyMemory<char> className, out ReadOnlyMemory<char> rest)
|
||||
{
|
||||
var firstChar = className.Span[0];
|
||||
rest = className[1..];
|
||||
|
||||
switch (firstChar)
|
||||
{
|
||||
// Init intrinsics
|
||||
case 'Z':
|
||||
return _classes.GetOrAdd("Z".AsMemory(), _ => new Class(this)
|
||||
{
|
||||
Name = "boolean".AsMemory(),
|
||||
Namespace = "java/lang".AsMemory(),
|
||||
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Boolean)
|
||||
});
|
||||
case 'B':
|
||||
return _classes.GetOrAdd("B".AsMemory(), _ => new Class(this)
|
||||
{
|
||||
Name = "byte".AsMemory(),
|
||||
Namespace = "java/lang".AsMemory(),
|
||||
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Byte)
|
||||
});
|
||||
case 'C':
|
||||
return _classes.GetOrAdd("C".AsMemory(), _ => new Class(this)
|
||||
{
|
||||
Name = "char".AsMemory(),
|
||||
Namespace = "java/lang".AsMemory(),
|
||||
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Char)
|
||||
});
|
||||
case 'S':
|
||||
return _classes.GetOrAdd("S".AsMemory(), _ => new Class(this)
|
||||
{
|
||||
Name = "short".AsMemory(),
|
||||
Namespace = "java/lang".AsMemory(),
|
||||
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Short)
|
||||
});
|
||||
case 'I':
|
||||
return _classes.GetOrAdd("I".AsMemory(), _ => new Class(this)
|
||||
{
|
||||
Name = "int".AsMemory(),
|
||||
Namespace = "java/lang".AsMemory(),
|
||||
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Int)
|
||||
});
|
||||
case 'J':
|
||||
return _classes.GetOrAdd("J".AsMemory(), _ => new Class(this)
|
||||
{
|
||||
Name = "long".AsMemory(),
|
||||
Namespace = "java/lang".AsMemory(),
|
||||
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Long)
|
||||
});
|
||||
case 'F':
|
||||
return _classes.GetOrAdd("F".AsMemory(), _ => new Class(this)
|
||||
{
|
||||
Name = "float".AsMemory(),
|
||||
Namespace = "java/lang".AsMemory(),
|
||||
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Float)
|
||||
});
|
||||
case 'D':
|
||||
return _classes.GetOrAdd("D".AsMemory(), _ => new Class(this)
|
||||
{
|
||||
Name = "double".AsMemory(),
|
||||
Namespace = "java/lang".AsMemory(),
|
||||
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Double)
|
||||
});
|
||||
case 'V':
|
||||
return _classes.GetOrAdd("V".AsMemory(), _ => new Class(this)
|
||||
{
|
||||
Name = "void".AsMemory(),
|
||||
Namespace = "java/lang".AsMemory(),
|
||||
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Void)
|
||||
});
|
||||
|
||||
case 'L' when className.Span.IndexOf('<') is var genericsStart and > 0:
|
||||
{
|
||||
var theName = className.Span;
|
||||
var semicolon = theName.IndexOf(';');
|
||||
if (semicolon < genericsStart)
|
||||
{
|
||||
className = className[..(semicolon + 1)];
|
||||
rest = rest[semicolon..];
|
||||
return GetClass(className);
|
||||
}
|
||||
|
||||
className = rest[..(genericsStart - 1)];
|
||||
rest = rest[genericsStart..];
|
||||
|
||||
var loop = true;
|
||||
var generics = new List<Class>();
|
||||
while (loop)
|
||||
{
|
||||
switch (rest.Span[0])
|
||||
{
|
||||
case '>': loop = false; break;
|
||||
case '*':
|
||||
generics.Add(Object);
|
||||
rest = rest[1..];
|
||||
break;
|
||||
default: generics.Add(GetClass(rest, out rest)); break;
|
||||
}
|
||||
}
|
||||
|
||||
rest = rest[2..];
|
||||
|
||||
return LoadClass(className).WithGenerics(generics);
|
||||
}
|
||||
|
||||
case 'L':
|
||||
{
|
||||
className = rest[..rest.Span.IndexOf(';')];
|
||||
rest = rest[(className.Length + 1)..];
|
||||
return LoadClass(className);
|
||||
}
|
||||
|
||||
case 'T':
|
||||
{
|
||||
className = rest[..rest.Span.IndexOf(';')];
|
||||
rest = rest[(className.Length + 1)..];
|
||||
// TODO Actually load a type from context
|
||||
return Object;
|
||||
}
|
||||
|
||||
case '[':
|
||||
{
|
||||
var arrayName = rest;
|
||||
var arrayElem = GetClass(arrayName, out rest);
|
||||
arrayName = arrayName[..^rest.Length];
|
||||
arrayName = className[..(arrayName.Length + 1)];
|
||||
return _classes.GetOrAdd(arrayName, _ =>
|
||||
{
|
||||
var arrClass = new Class(this)
|
||||
{
|
||||
Name = arrayName,
|
||||
SuperClass = Object,
|
||||
SpecialClassMetadata = new ArrayMetadata(arrayElem),
|
||||
};
|
||||
return arrClass;
|
||||
});
|
||||
}
|
||||
|
||||
case '(':
|
||||
{
|
||||
var args = new List<Class>();
|
||||
while (rest.Span[0] != ')') args.Add(GetClass(rest, out rest));
|
||||
rest = rest[1..];
|
||||
var retClass = GetClass(rest, out rest);
|
||||
|
||||
className = className[..^rest.Length];
|
||||
return _classes.GetOrAdd(className, _ => new Class(this)
|
||||
{
|
||||
SpecialClassMetadata = new MethodSignature(retClass, args)
|
||||
});
|
||||
}
|
||||
|
||||
// TODO Handle this somehow
|
||||
case '+': return GetClass(rest, out rest);
|
||||
case '-': return GetClass(rest, out rest);
|
||||
|
||||
default:
|
||||
{
|
||||
rest = default;
|
||||
return LoadClass(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Class LoadClass(ReadOnlyMemory<char> className)
|
||||
{
|
||||
var mustLoad = false;
|
||||
var javaClass = _classes.GetOrAdd(className, _ =>
|
||||
{
|
||||
mustLoad = true;
|
||||
var javaClass = new Class(this);
|
||||
Monitor.Enter(javaClass);
|
||||
return javaClass;
|
||||
});
|
||||
|
||||
if (mustLoad)
|
||||
{
|
||||
if (!_files.TryGetValue(className, out var classFile))
|
||||
throw new FileNotFoundException($"{className}.class was not found");
|
||||
|
||||
// Console.WriteLine($"Loading class {className}");
|
||||
using var file = File.OpenRead(classFile);
|
||||
using var reader = new BufferedStream(file);
|
||||
javaClass.Read(reader);
|
||||
Monitor.Exit(javaClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (javaClass)
|
||||
{
|
||||
// Wait for it to be loaded
|
||||
}
|
||||
}
|
||||
|
||||
return javaClass;
|
||||
}
|
||||
|
||||
private static ReadOnlyMemory<char> SliceFromSpan(ReadOnlyMemory<char> memory, ReadOnlySpan<char> span)
|
||||
{
|
||||
var mem = memory.Span;
|
||||
var start = Unsafe.ByteOffset(in mem[0], in span[0]) / sizeof(char);
|
||||
return memory.Slice((int)start, span.Length);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ReadOnlyMemoryCharComparer : IEqualityComparer<ReadOnlyMemory<char>>
|
||||
{
|
||||
public static readonly ReadOnlyMemoryCharComparer Instance = new();
|
||||
|
||||
public bool Equals(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y)
|
||||
=> x.Span.SequenceEqual(y.Span);
|
||||
|
||||
public int GetHashCode(ReadOnlyMemory<char> obj)
|
||||
=> string.GetHashCode(obj.Span);
|
||||
}
|
||||
Reference in New Issue
Block a user