Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 706f09dfc0 |
@@ -1,8 +0,0 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
*.dll
|
||||
*.class
|
||||
*.java
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
||||
<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>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\JavaClass\JavaClass.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,258 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI;
|
||||
|
||||
public sealed class Block
|
||||
{
|
||||
public Class? CatchType { get; init; }
|
||||
public required ReadOnlyMemory<OpCode> OpCodes { get; init; }
|
||||
public IReadOnlyList<Class> Locals { get; private set; } = [];
|
||||
public IReadOnlyList<Class> Stack { get; private set; } = [];
|
||||
|
||||
public int Start => OpCodes.Span[0].Position;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Block_{OpCodes.Span[0].Position:X04}";
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CodeAnalysis
|
||||
{
|
||||
public required SortedDictionary<int, Block> Blocks { get; init; }
|
||||
|
||||
public static CodeAnalysis Analyze(TypeLoader loader, Method method)
|
||||
{
|
||||
if (!method.TryGetCode(out var code))
|
||||
throw new ArgumentException("Method does not have a body.");
|
||||
|
||||
using var reader = code.CreateOpCodeReader();
|
||||
var opcodes = reader.ToArray();
|
||||
|
||||
Class? catchType = null;
|
||||
var blocks = new SortedDictionary<int, Block>();
|
||||
for (int i = 0, start = 0, count = 0; i < opcodes.Length; i++, count++)
|
||||
{
|
||||
ref readonly var opcode = ref opcodes[i];
|
||||
switch (opcode.Mnemonic)
|
||||
{
|
||||
case OpCodeMnemonic.IfEq:
|
||||
case OpCodeMnemonic.IfNe:
|
||||
case OpCodeMnemonic.IfLt:
|
||||
case OpCodeMnemonic.IfGt:
|
||||
case OpCodeMnemonic.IfLe:
|
||||
case OpCodeMnemonic.IfGe:
|
||||
case OpCodeMnemonic.IfICmpEq:
|
||||
case OpCodeMnemonic.IfICmpNe:
|
||||
case OpCodeMnemonic.IfICmpLt:
|
||||
case OpCodeMnemonic.IfICmpGt:
|
||||
case OpCodeMnemonic.IfICmpLe:
|
||||
case OpCodeMnemonic.IfICmpGe:
|
||||
case OpCodeMnemonic.IfACmpEq:
|
||||
case OpCodeMnemonic.IfACmpNe:
|
||||
case OpCodeMnemonic.Goto:
|
||||
case OpCodeMnemonic.IReturn:
|
||||
case OpCodeMnemonic.LReturn:
|
||||
case OpCodeMnemonic.FReturn:
|
||||
case OpCodeMnemonic.DReturn:
|
||||
case OpCodeMnemonic.AReturn:
|
||||
case OpCodeMnemonic.Return:
|
||||
case OpCodeMnemonic.AThrow:
|
||||
case OpCodeMnemonic.IfNull:
|
||||
case OpCodeMnemonic.IfNonNull:
|
||||
case OpCodeMnemonic.TableSwitch:
|
||||
case OpCodeMnemonic.LookupSwitch:
|
||||
{
|
||||
var blockOpCodes = opcodes.AsMemory(start, ++count);
|
||||
blocks.Add(opcodes[start].Position, new Block { OpCodes = blockOpCodes, CatchType = catchType });
|
||||
|
||||
if (i != opcodes.Length - 1)
|
||||
{
|
||||
var nextPosition = opcodes[i + 1].Position;
|
||||
catchType = code.ExceptionTable.FirstOrDefault(e => e.Handler == nextPosition).CatchType;
|
||||
}
|
||||
|
||||
start = i + 1;
|
||||
count = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Block ResolveBlock(int addr)
|
||||
{
|
||||
if (blocks.TryGetValue(addr, out var block))
|
||||
return block;
|
||||
|
||||
// Split block if an instruction jumps in the middle of it
|
||||
|
||||
foreach (var (key, nextBlock) in blocks)
|
||||
{
|
||||
if(key <= addr) block = nextBlock;
|
||||
else break;
|
||||
}
|
||||
|
||||
var span = block!.OpCodes.Span;
|
||||
for (var i = 0; i < span.Length; i++)
|
||||
{
|
||||
if (span[i].Position != addr) continue;
|
||||
blocks[span[0].Position] = new Block
|
||||
{
|
||||
OpCodes = block.OpCodes[..i],
|
||||
CatchType = block.CatchType,
|
||||
};
|
||||
return blocks[addr] = new Block
|
||||
{
|
||||
OpCodes = block.OpCodes[i..],
|
||||
CatchType = block.CatchType,
|
||||
};
|
||||
}
|
||||
|
||||
throw new UnreachableException();
|
||||
}
|
||||
|
||||
foreach (ref var opcode in opcodes.AsSpan())
|
||||
{
|
||||
switch (opcode.Mnemonic)
|
||||
{
|
||||
case OpCodeMnemonic.IfEq:
|
||||
case OpCodeMnemonic.IfNe:
|
||||
case OpCodeMnemonic.IfLt:
|
||||
case OpCodeMnemonic.IfGt:
|
||||
case OpCodeMnemonic.IfLe:
|
||||
case OpCodeMnemonic.IfGe:
|
||||
case OpCodeMnemonic.IfICmpEq:
|
||||
case OpCodeMnemonic.IfICmpNe:
|
||||
case OpCodeMnemonic.IfICmpLt:
|
||||
case OpCodeMnemonic.IfICmpGt:
|
||||
case OpCodeMnemonic.IfICmpLe:
|
||||
case OpCodeMnemonic.IfICmpGe:
|
||||
case OpCodeMnemonic.IfACmpEq:
|
||||
case OpCodeMnemonic.IfACmpNe:
|
||||
case OpCodeMnemonic.Goto:
|
||||
case OpCodeMnemonic.IfNull:
|
||||
case OpCodeMnemonic.IfNonNull:
|
||||
{
|
||||
opcode.P0.Object = ResolveBlock(opcode.Position + (short)opcode.P0.UShort);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.TableSwitch:
|
||||
{
|
||||
var tableSwitch = (TableSwitch) opcode.P0.Object!;
|
||||
var targets = new Block[tableSwitch.Offsets.Length + 1];
|
||||
opcode.P1.Object = targets;
|
||||
targets[0] = ResolveBlock(opcode.Position + tableSwitch.Default);
|
||||
for (var i = 0; i < tableSwitch.Offsets.Length; i++)
|
||||
targets[i + 1] = ResolveBlock(opcode.Position + tableSwitch.Offsets[i]);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.LookupSwitch:
|
||||
{
|
||||
var tableSwitch = (LookupSwitch) opcode.P0.Object!;
|
||||
var targets = new Block[tableSwitch.Entries.Length + 1];
|
||||
opcode.P1.Object = targets;
|
||||
targets[0] = ResolveBlock(opcode.Position + tableSwitch.Default);
|
||||
for (var i = 0; i < tableSwitch.Entries.Length; i++)
|
||||
targets[i + 1] = ResolveBlock(opcode.Position + tableSwitch.Entries[i].Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(code.Attributes.OfType<StackMapTable>().FirstOrDefault() is not {} stackMap)
|
||||
return new CodeAnalysis { Blocks = blocks };
|
||||
|
||||
foreach (var frame in stackMap.Frames)
|
||||
ResolveBlock(frame.StartOffset);
|
||||
|
||||
var stack = new List<Class>(code.MaxStack);
|
||||
var locals = new List<Class>(code.MaxLocals);
|
||||
if((method.AccessFlags & MethodAccessFlags.Static) == 0)
|
||||
locals.Add(method.DeclaringClass);
|
||||
|
||||
Class ResolveClass(StackMapTable.VerificationTypeInfo info)
|
||||
{
|
||||
switch (info.Tag)
|
||||
{
|
||||
case StackMapTable.VerificationTypeInfoTag.Top: return locals[^1];
|
||||
case StackMapTable.VerificationTypeInfoTag.Integer: return loader.Int;
|
||||
case StackMapTable.VerificationTypeInfoTag.Float: return loader.Float;
|
||||
case StackMapTable.VerificationTypeInfoTag.Double: return loader.Double;
|
||||
case StackMapTable.VerificationTypeInfoTag.Long: return loader.Long;
|
||||
case StackMapTable.VerificationTypeInfoTag.Object: return method.DeclaringClass.GetConstant<Class>(info.Parameter);
|
||||
case StackMapTable.VerificationTypeInfoTag.Null:
|
||||
case StackMapTable.VerificationTypeInfoTag.UninitializedThis:
|
||||
case StackMapTable.VerificationTypeInfoTag.Uninitialized:
|
||||
default: throw new NotImplementedException(info.Tag.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
locals.AddRange(method.Signature.ParamTypes);
|
||||
foreach (var block in blocks.Values)
|
||||
{
|
||||
if(block.Start >= stackMap.Frames[0].StartOffset) break;
|
||||
LocalsRef(block) = locals.ToArray();
|
||||
}
|
||||
|
||||
for (var i = 0; i < stackMap.Frames.Count; i++)
|
||||
{
|
||||
var frame = stackMap.Frames[i];
|
||||
var end = i == stackMap.Frames.Count - 1 ? code.Bytecode.Length : stackMap.Frames[i + 1].StartOffset;
|
||||
switch (frame)
|
||||
{
|
||||
case StackMapTable.SameFrame:
|
||||
case StackMapTable.SameFrameExtended:
|
||||
stack.Clear();
|
||||
break;
|
||||
|
||||
case StackMapTable.FullFrame f:
|
||||
stack.Clear();
|
||||
locals.Clear();
|
||||
locals.AddRange(f.Locals.Select(ResolveClass));
|
||||
stack.AddRange(f.StackItems.Select(ResolveClass));
|
||||
break;
|
||||
|
||||
case StackMapTable.ChopFrame f:
|
||||
stack.Clear();
|
||||
locals.RemoveRange(locals.Count - f.Count, f.Count);
|
||||
break;
|
||||
|
||||
case StackMapTable.AppendFrame f:
|
||||
stack.Clear();
|
||||
locals.AddRange(f.Locals.Select(ResolveClass));
|
||||
break;
|
||||
|
||||
case StackMapTable.OneTempFrame f:
|
||||
stack.Clear();
|
||||
stack.Add(ResolveClass(f.Entry));
|
||||
break;
|
||||
|
||||
case StackMapTable.OneTempFrameExtended f:
|
||||
stack.Clear();
|
||||
stack.Add(ResolveClass(f.Entry));
|
||||
break;
|
||||
|
||||
default: throw new NotImplementedException(frame.ToString());
|
||||
}
|
||||
foreach (var block in blocks.Values)
|
||||
{
|
||||
if(block.Start < frame.StartOffset) continue;
|
||||
if(block.Start >= end) break;
|
||||
LocalsRef(block) = locals.ToArray();
|
||||
StackRef(block) = stack.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
return new CodeAnalysis { Blocks = blocks };
|
||||
}
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "<Locals>k__BackingField")]
|
||||
private static extern ref IReadOnlyList<Class> LocalsRef(Block block);
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "<Stack>k__BackingField")]
|
||||
private static extern ref IReadOnlyList<Class> StackRef(Block block);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.CodeDom.Compiler;
|
||||
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(loader, new AssemblyName("Test"), false);
|
||||
var dotnetClass = builder.MakeType(javaClass);
|
||||
try
|
||||
{
|
||||
dotnetClass.GetMethod(javaClass.Methods[1]);
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
Console.WriteLine($"Compiled methods: {builder.CompiledMethods}");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Error.WriteLine(e.Message);
|
||||
Environment.Exit(1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.WriteLine($"Compiled methods: {builder.CompiledMethods}");
|
||||
var assembly = (PersistedAssemblyBuilder)builder.Assembly;
|
||||
assembly.Save("out.dll");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Debug
|
||||
{
|
||||
public static readonly IndentedTextWriter Writer = new(Console.Error, "| ");
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types;
|
||||
|
||||
public class NewClass : TypeRepresentation
|
||||
{
|
||||
public readonly Class Original;
|
||||
public override Type Type => TypeBuilder;
|
||||
|
||||
public readonly TypeBuilder TypeBuilder;
|
||||
protected readonly AssemblyBuilder Builder;
|
||||
|
||||
private FrozenDictionary<string, FieldInfo> _fields = FrozenDictionary<string, FieldInfo>.Empty;
|
||||
protected readonly ConcurrentDictionary<(string, MethodSignature), MethodBase> Methods = [];
|
||||
|
||||
public NewClass(AssemblyBuilder builder, TypeBuilder type, Class original)
|
||||
{
|
||||
TypeBuilder = type;
|
||||
Builder = builder;
|
||||
Original = original;
|
||||
}
|
||||
|
||||
internal override Value GetField(string name, ILGenerator il)
|
||||
{
|
||||
var field = _fields[name];
|
||||
il.Emit(field.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, field);
|
||||
return new Value(field.FieldType);
|
||||
}
|
||||
|
||||
internal override void SetField(string name, Value value, ILGenerator il)
|
||||
{
|
||||
var field = _fields[name];
|
||||
Builder.AssertCast(value, field.FieldType, il);
|
||||
il.Emit(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field);
|
||||
}
|
||||
|
||||
public override MethodBase GetMethod(Method method)
|
||||
{
|
||||
return GetMethod(method.Name, (MethodSignature) method.Type.SpecialClassMetadata!);
|
||||
}
|
||||
|
||||
public virtual MethodBase GetMethod(string name, MethodSignature signature)
|
||||
{
|
||||
Method? original = null;
|
||||
var method = Methods.GetOrAdd((name, signature), _ =>
|
||||
{
|
||||
original = Original.GetMethod(name, signature);
|
||||
if (original == null) throw new KeyNotFoundException($"Could not find method `{name}`");
|
||||
|
||||
MethodAttributes attributes = default;
|
||||
if ((original.AccessFlags & MethodAccessFlags.Final) != 0) attributes |= MethodAttributes.Final;
|
||||
if ((original.AccessFlags & MethodAccessFlags.Static) != 0) attributes |= MethodAttributes.Static;
|
||||
if ((original.AccessFlags & MethodAccessFlags.Public) != 0) attributes |= MethodAttributes.Public;
|
||||
if ((original.AccessFlags & MethodAccessFlags.Private) != 0) attributes |= MethodAttributes.Private;
|
||||
if ((original.AccessFlags & MethodAccessFlags.Protected) != 0) attributes |= MethodAttributes.Family;
|
||||
|
||||
signature = (MethodSignature) original.Type.SpecialClassMetadata!;
|
||||
if (original.Name == "<init>")
|
||||
{
|
||||
return TypeBuilder.DefineConstructor(
|
||||
attributes, CallingConventions.Standard,
|
||||
signature.ParamTypes.Select(t => Builder.MakeType(t).Type).ToArray()
|
||||
);
|
||||
}
|
||||
|
||||
return TypeBuilder.DefineMethod(
|
||||
name, attributes, CallingConventions.Standard,
|
||||
Builder.MakeType(signature.ReturnType).Type,
|
||||
signature.ParamTypes.Select(t => Builder.MakeType(t).Type).ToArray()
|
||||
);
|
||||
});
|
||||
|
||||
if (original is null)
|
||||
return method;
|
||||
|
||||
if (!original.TryGetCode(out var code))
|
||||
return method;
|
||||
|
||||
var blocks = CodeAnalysis.Analyze(Builder.TypeLoader, original);
|
||||
|
||||
if (method is MethodBuilder methodBuilder)
|
||||
Builder.CompileOpCodes(original.DeclaringClass, method, methodBuilder.GetILGenerator(), code);
|
||||
|
||||
if (method is ConstructorBuilder constructorBuilder)
|
||||
Builder.CompileOpCodes(original.DeclaringClass, method, constructorBuilder.GetILGenerator(), code);
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
internal override Type CallMethod(string name, MethodSignature signature, Stack<Value> stack, ILGenerator il)
|
||||
{
|
||||
var method = GetMethod(name, signature);
|
||||
if (!method.IsStatic) stack.Pop();
|
||||
for (var i = 0; i < signature.ParamTypes.Count; i++) stack.Pop();
|
||||
|
||||
Type type;
|
||||
switch (method)
|
||||
{
|
||||
case MethodInfo info when method.IsVirtual:
|
||||
il.Emit(OpCodes.Callvirt, info);
|
||||
type = info.ReturnType;
|
||||
break;
|
||||
|
||||
case MethodInfo info:
|
||||
il.Emit(OpCodes.Call, info);
|
||||
type = info.ReturnType;
|
||||
break;
|
||||
|
||||
case ConstructorInfo info:
|
||||
il.Emit(OpCodes.Call, info);
|
||||
type = typeof(void);
|
||||
break;
|
||||
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
|
||||
if(type != typeof(void))
|
||||
stack.Push(new Value(type));
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
internal void CreateFields()
|
||||
{
|
||||
var fields = new Dictionary<string, FieldInfo>();
|
||||
foreach (var field in Original.Fields)
|
||||
{
|
||||
FieldAttributes attributes = default;
|
||||
if ((field.AccessFlags & FieldAccessFlags.Static) != 0) attributes |= FieldAttributes.Static;
|
||||
if ((field.AccessFlags & FieldAccessFlags.Final) != 0) attributes |= FieldAttributes.InitOnly;
|
||||
if ((field.AccessFlags & FieldAccessFlags.Public) != 0) attributes |= FieldAttributes.Public;
|
||||
if ((field.AccessFlags & FieldAccessFlags.Private) != 0) attributes |= FieldAttributes.Private;
|
||||
if ((field.AccessFlags & FieldAccessFlags.Protected) != 0) attributes |= FieldAttributes.Family;
|
||||
var type = Builder.MakeType(field.Type);
|
||||
var fieldInfo = TypeBuilder.DefineField(field.Name, type.Type, attributes);
|
||||
fields.Add(field.Name, fieldInfo);
|
||||
}
|
||||
|
||||
_fields = fields.ToFrozenDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TypeSurrogate : NewClass
|
||||
{
|
||||
public abstract override Type Type { get; }
|
||||
private readonly ConcurrentDictionary<MethodBase, bool> _methods = new();
|
||||
|
||||
protected TypeSurrogate(AssemblyBuilder builder, Class original) : base(builder, Create(builder, original), original)
|
||||
{
|
||||
CreateFields();
|
||||
}
|
||||
|
||||
public virtual MethodBase GetMethod(string name, MethodSignature signature)
|
||||
{
|
||||
Method? original = null;
|
||||
var method = Methods.GetOrAdd((name, signature), _ =>
|
||||
{
|
||||
original = Original.GetMethod(name, signature);
|
||||
if (original == null) throw new KeyNotFoundException($"Could not find method `{name}`");
|
||||
|
||||
var attributes = MethodAttributes.Static;
|
||||
if ((original.AccessFlags & MethodAccessFlags.Final) != 0) attributes |= MethodAttributes.Final;
|
||||
if ((original.AccessFlags & MethodAccessFlags.Public) != 0) attributes |= MethodAttributes.Public;
|
||||
if ((original.AccessFlags & MethodAccessFlags.Private) != 0) attributes |= MethodAttributes.Private;
|
||||
if ((original.AccessFlags & MethodAccessFlags.Protected) != 0) attributes |= MethodAttributes.Family;
|
||||
|
||||
signature = (MethodSignature) original.Type.SpecialClassMetadata!;
|
||||
var parameters = signature.ParamTypes.Select(t => Builder.MakeType(t).Type).ToList();
|
||||
if ((original.AccessFlags & MethodAccessFlags.Static) == 0)
|
||||
parameters.Insert(0, Type);
|
||||
|
||||
var method = TypeBuilder.DefineMethod(
|
||||
name, attributes, CallingConventions.Standard,
|
||||
Builder.MakeType(signature.ReturnType).Type,
|
||||
parameters.ToArray()
|
||||
);
|
||||
|
||||
_methods[method] = (original.AccessFlags & MethodAccessFlags.Static) != 0;
|
||||
return method;
|
||||
});
|
||||
|
||||
if (original is null)
|
||||
return method;
|
||||
|
||||
if (!original.TryGetCode(out var code))
|
||||
return method;
|
||||
|
||||
var methodBuilder = (MethodBuilder) method;
|
||||
Builder.CompileOpCodes(Original, method, methodBuilder.GetILGenerator(), code);
|
||||
return method;
|
||||
}
|
||||
|
||||
internal override Type CallMethod(string name, MethodSignature signature, Stack<Value> stack, ILGenerator il)
|
||||
{
|
||||
var method = GetMethod(name, signature);
|
||||
if (!_methods[method]) stack.Pop();
|
||||
for (var i = 0; i < signature.ParamTypes.Count; i++) stack.Pop();
|
||||
|
||||
Type type;
|
||||
switch (method)
|
||||
{
|
||||
case MethodInfo info when method.IsVirtual:
|
||||
il.Emit(OpCodes.Callvirt, info);
|
||||
type = info.ReturnType;
|
||||
break;
|
||||
|
||||
case MethodInfo info:
|
||||
il.Emit(OpCodes.Call, info);
|
||||
type = info.ReturnType;
|
||||
break;
|
||||
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
|
||||
if(type != typeof(void))
|
||||
stack.Push(new Value(type));
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private static TypeBuilder Create(AssemblyBuilder builder, Class original)
|
||||
{
|
||||
TypeAttributes flags = default;
|
||||
if ((original.AccessFlags & ClassAccessFlags.Public) != 0) flags |= TypeAttributes.Public;
|
||||
if ((original.AccessFlags & ClassAccessFlags.Abstract) != 0) flags |= TypeAttributes.Abstract;
|
||||
if ((original.AccessFlags & ClassAccessFlags.Final) != 0) flags |= TypeAttributes.Sealed;
|
||||
if ((original.AccessFlags & ClassAccessFlags.Interface) != 0) flags |= TypeAttributes.Interface;
|
||||
|
||||
var name = original.Namespace.IsEmpty
|
||||
? original.Name.ToString()
|
||||
: $"{original.Namespace.ToString().Replace('/', '.')}.{original.Name}";
|
||||
|
||||
var type = builder.Module.DefineType(name, flags);
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types;
|
||||
|
||||
public abstract class TypeRepresentation
|
||||
{
|
||||
public abstract Type Type { get; }
|
||||
|
||||
internal virtual Value GetField(string name, ILGenerator il)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
internal virtual void SetField(string name, Value value, ILGenerator il)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public abstract MethodBase GetMethod(Method method);
|
||||
|
||||
internal abstract Type CallMethod(string name, MethodSignature signature, Stack<Value> stack, ILGenerator il);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public class Array(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(System.Array);
|
||||
}
|
||||
|
||||
public sealed class ArrayOf(AssemblyBuilder builder, TypeRepresentation elementType, Class javaClass) : Array(builder, javaClass)
|
||||
{
|
||||
public override Type Type { get; } = elementType.Type.MakeArrayType();
|
||||
public readonly TypeRepresentation ElementType = elementType;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class Boolean(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(bool);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class Byte(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(byte);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class Char(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(char);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class Double(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(double);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class Float(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(float);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class Integer(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(int);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class Long(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(long);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class Object(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(object);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class Short(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(short);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class String : TypeSurrogate
|
||||
{
|
||||
public override Type Type => typeof(string);
|
||||
|
||||
public String(AssemblyBuilder builder, Class javaClass) : base(builder, javaClass)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using JCIL.Java.Class;
|
||||
|
||||
namespace JCIL.CLI.Types.Java.Lang;
|
||||
|
||||
public sealed class Void(AssemblyBuilder builder, Class javaClass) : TypeSurrogate(builder, javaClass)
|
||||
{
|
||||
public override Type Type => typeof(void);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace JCIL.Core;
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
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;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static int FCmpL(float a, float b)
|
||||
{
|
||||
if (float.IsNaN(a) || float.IsNaN(b)) return -1;
|
||||
if (a > b) return 1;
|
||||
if (a < b) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static int FCmpG(float a, float b)
|
||||
{
|
||||
if (float.IsNaN(a) || float.IsNaN(b)) return 1;
|
||||
if (a > b) return 1;
|
||||
if (a < b) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static bool ICmpGe(int a, int b)
|
||||
{
|
||||
return a >= b;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static bool ICmpLe(int a, int b)
|
||||
{
|
||||
return a <= b;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,258 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
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 OpCodeReader CreateOpCodeReader()
|
||||
{
|
||||
MemoryMarshal.TryGetArray(Bytecode, out var array);
|
||||
return new OpCodeReader(new MemoryStream(array.Array!, array.Offset, array.Count));
|
||||
}
|
||||
|
||||
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 OneTempFrame(offset, VerificationTypeInfo.Read(reader));
|
||||
break;
|
||||
|
||||
case 247:
|
||||
delta = reader.ReadUInt16() + one;
|
||||
offset += delta;
|
||||
frame = new OneTempFrameExtended(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 OneTempFrame(int StartOffset, VerificationTypeInfo Entry) : Frame(StartOffset);
|
||||
|
||||
public sealed record OneTempFrameExtended(int StartOffset, VerificationTypeInfo Entry) : Frame(StartOffset);
|
||||
|
||||
public sealed record AppendFrame(int StartOffset, VerificationTypeInfo[] Locals) : Frame(StartOffset);
|
||||
|
||||
public sealed record FullFrame(
|
||||
int StartOffset,
|
||||
VerificationTypeInfo[] Locals,
|
||||
VerificationTypeInfo[] StackItems
|
||||
) : Frame(StartOffset);
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
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 T GetConstant<T>(ushort i)
|
||||
{
|
||||
return (T) 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}";
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<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>
|
||||
@@ -1,647 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
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 Class DeclaringClass;
|
||||
public readonly MethodAccessFlags AccessFlags;
|
||||
public readonly string Name;
|
||||
public readonly Class Type;
|
||||
public readonly IReadOnlyList<Attribute> Attributes;
|
||||
public MethodSignature Signature => Unsafe.As<MethodSignature>(Type.SpecialClassMetadata)!;
|
||||
|
||||
private Method(Class declaringClass, MethodAccessFlags flags, string name, Class type, Attribute[] attributes)
|
||||
{
|
||||
DeclaringClass = declaringClass;
|
||||
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(parent, flags, name, descriptorClass, attributes);
|
||||
}
|
||||
|
||||
public bool TryGetCode([MaybeNullWhen(false)] out Code code)
|
||||
{
|
||||
if (Attributes.OfType<Code>().FirstOrDefault() is {} attr)
|
||||
{
|
||||
code = attr;
|
||||
return true;
|
||||
}
|
||||
|
||||
code = null!;
|
||||
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 int Position;
|
||||
public OpCodeParameter P0;
|
||||
public OpCodeParameter P1;
|
||||
|
||||
public string ToString(Class javaClass)
|
||||
{
|
||||
switch (Mnemonic)
|
||||
{
|
||||
case OpCodeMnemonic.GetStaticField:
|
||||
{
|
||||
var fieldRef = javaClass.GetConstant<FieldRef>(P0.UShort);
|
||||
return $"{Mnemonic} {fieldRef.Class}.{fieldRef.Field.Name}";
|
||||
}
|
||||
|
||||
case OpCodeMnemonic.InvokeStatic:
|
||||
case OpCodeMnemonic.InvokeVirtual:
|
||||
case OpCodeMnemonic.InvokeSpecial:
|
||||
{
|
||||
var methodRef = javaClass.GetConstant<MethodRef>(P0.UShort);
|
||||
return $"{Mnemonic} {methodRef.Class}.{methodRef.Method.Name}";
|
||||
}
|
||||
|
||||
default: return ToString();
|
||||
}
|
||||
}
|
||||
|
||||
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 sealed class OpCodeReader(Stream bytecode) : IEnumerator<OpCode>, IEnumerable<OpCode>
|
||||
{
|
||||
private OpCode _current;
|
||||
public Reader Reader = new(bytecode);
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
var position = Reader.Stream.Position;
|
||||
var b = Reader.Stream.ReadByte();
|
||||
if (b == -1)
|
||||
{
|
||||
_current = default;
|
||||
return false;
|
||||
}
|
||||
_current = new OpCode { Mnemonic = (OpCodeMnemonic)(byte)b, Position = (int) position };
|
||||
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 OpCodeReader GetEnumerator()
|
||||
=> this;
|
||||
|
||||
IEnumerator<OpCode> IEnumerable<OpCode>.GetEnumerator()
|
||||
=> this;
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> this;
|
||||
}
|
||||
|
||||
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 = [];
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
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 Void => field ??= GetClass("V".AsMemory());
|
||||
public Class Char => field ??= GetClass("C".AsMemory());
|
||||
public Class Byte => field ??= GetClass("B".AsMemory());
|
||||
public Class Short => field ??= GetClass("S".AsMemory());
|
||||
public Class Int => field ??= GetClass("I".AsMemory());
|
||||
public Class Long => field ??= GetClass("J".AsMemory());
|
||||
public Class Float => field ??= GetClass("F".AsMemory());
|
||||
public Class Double => field ??= GetClass("D".AsMemory());
|
||||
public Class Boolean => field ??= GetClass("Z".AsMemory());
|
||||
public Class Object => field ??= LoadClass("java/lang/Object".AsMemory());
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 shy_mia
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Reference in New Issue
Block a user