283 lines
8.6 KiB
C#
Executable File
283 lines
8.6 KiB
C#
Executable File
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}";
|
|
} |