Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Reflection;
|
||||
|
||||
internal sealed record JavaReflectionAnalysis(
|
||||
ImmutableArray<JavaReflectionEdge> Edges,
|
||||
ImmutableArray<JavaReflectionWarning> Warnings)
|
||||
{
|
||||
public static readonly JavaReflectionAnalysis Empty = new(ImmutableArray<JavaReflectionEdge>.Empty, ImmutableArray<JavaReflectionWarning>.Empty);
|
||||
}
|
||||
|
||||
internal sealed record JavaReflectionEdge(
|
||||
string SourceClass,
|
||||
string SegmentIdentifier,
|
||||
string? TargetType,
|
||||
JavaReflectionReason Reason,
|
||||
JavaReflectionConfidence Confidence,
|
||||
string MethodName,
|
||||
string MethodDescriptor,
|
||||
int InstructionOffset,
|
||||
string? Details);
|
||||
|
||||
internal sealed record JavaReflectionWarning(
|
||||
string SourceClass,
|
||||
string SegmentIdentifier,
|
||||
string WarningCode,
|
||||
string Message,
|
||||
string MethodName,
|
||||
string MethodDescriptor);
|
||||
|
||||
internal enum JavaReflectionReason
|
||||
{
|
||||
ClassForName,
|
||||
ClassLoaderLoadClass,
|
||||
ServiceLoaderLoad,
|
||||
ResourceLookup,
|
||||
}
|
||||
|
||||
internal enum JavaReflectionConfidence
|
||||
{
|
||||
Low = 1,
|
||||
Medium = 2,
|
||||
High = 3,
|
||||
}
|
||||
@@ -0,0 +1,716 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.ClassPath;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Reflection;
|
||||
|
||||
internal static class JavaReflectionAnalyzer
|
||||
{
|
||||
public static JavaReflectionAnalysis Analyze(JavaClassPathAnalysis classPath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(classPath);
|
||||
|
||||
if (classPath.Segments.IsDefaultOrEmpty)
|
||||
{
|
||||
return JavaReflectionAnalysis.Empty;
|
||||
}
|
||||
|
||||
var edges = new List<JavaReflectionEdge>();
|
||||
var warnings = new List<JavaReflectionWarning>();
|
||||
|
||||
foreach (var segment in classPath.Segments)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
foreach (var kvp in segment.ClassLocations)
|
||||
{
|
||||
var className = kvp.Key;
|
||||
var location = kvp.Value;
|
||||
|
||||
using var stream = location.OpenClassStream(cancellationToken);
|
||||
var classFile = JavaClassFile.Parse(stream, cancellationToken);
|
||||
|
||||
foreach (var method in classFile.Methods)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
AnalyzeMethod(classFile, method, segment.Identifier, className, edges, warnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (edges.Count == 0 && warnings.Count == 0)
|
||||
{
|
||||
return JavaReflectionAnalysis.Empty;
|
||||
}
|
||||
|
||||
return new JavaReflectionAnalysis(
|
||||
edges.ToImmutableArray(),
|
||||
warnings.ToImmutableArray());
|
||||
}
|
||||
|
||||
private static void AnalyzeMethod(
|
||||
JavaClassFile classFile,
|
||||
JavaMethod method,
|
||||
string segmentIdentifier,
|
||||
string className,
|
||||
List<JavaReflectionEdge> edges,
|
||||
List<JavaReflectionWarning> warnings)
|
||||
{
|
||||
var pool = classFile.ConstantPool;
|
||||
|
||||
string? pendingStringLiteral = null;
|
||||
string? pendingClassLiteral = null;
|
||||
var sawCurrentThread = false;
|
||||
var emittedTcclWarning = false;
|
||||
|
||||
var code = method.Code;
|
||||
var offset = 0;
|
||||
var length = code.Length;
|
||||
|
||||
while (offset < length)
|
||||
{
|
||||
var instructionOffset = offset;
|
||||
var opcode = code[offset++];
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case 0x12: // LDC
|
||||
{
|
||||
var index = code[offset++];
|
||||
HandleLdc(index, pool, ref pendingStringLiteral, ref pendingClassLiteral);
|
||||
break;
|
||||
}
|
||||
case 0x13: // LDC_W
|
||||
case 0x14: // LDC2_W
|
||||
{
|
||||
var index = (code[offset++] << 8) | code[offset++];
|
||||
HandleLdc(index, pool, ref pendingStringLiteral, ref pendingClassLiteral);
|
||||
break;
|
||||
}
|
||||
case 0xB8: // invokestatic
|
||||
case 0xB6: // invokevirtual
|
||||
case 0xB7: // invokespecial
|
||||
case 0xB9: // invokeinterface
|
||||
{
|
||||
var methodIndex = (code[offset++] << 8) | code[offset++];
|
||||
if (opcode == 0xB9)
|
||||
{
|
||||
offset += 2; // count and zero
|
||||
}
|
||||
|
||||
HandleInvocation(
|
||||
pool,
|
||||
method,
|
||||
segmentIdentifier,
|
||||
className,
|
||||
instructionOffset,
|
||||
methodIndex,
|
||||
opcode,
|
||||
ref pendingStringLiteral,
|
||||
ref pendingClassLiteral,
|
||||
ref sawCurrentThread,
|
||||
ref emittedTcclWarning,
|
||||
edges,
|
||||
warnings);
|
||||
|
||||
pendingStringLiteral = null;
|
||||
pendingClassLiteral = null;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (IsStoreInstruction(opcode))
|
||||
{
|
||||
pendingStringLiteral = null;
|
||||
pendingClassLiteral = null;
|
||||
|
||||
if (IsStoreWithExplicitIndex(opcode))
|
||||
{
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
else if (IsLoadInstructionWithIndex(opcode))
|
||||
{
|
||||
offset++;
|
||||
}
|
||||
else if (IsStackMutation(opcode))
|
||||
{
|
||||
pendingStringLiteral = null;
|
||||
pendingClassLiteral = null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When the method calls Thread.currentThread without accessing the context loader, we do not emit warnings.
|
||||
}
|
||||
|
||||
private static void HandleLdc(
|
||||
int constantIndex,
|
||||
JavaConstantPool pool,
|
||||
ref string? pendingString,
|
||||
ref string? pendingClassLiteral)
|
||||
{
|
||||
var constantKind = pool.GetConstantKind(constantIndex);
|
||||
switch (constantKind)
|
||||
{
|
||||
case JavaConstantKind.String:
|
||||
pendingString = pool.GetString(constantIndex);
|
||||
pendingClassLiteral = null;
|
||||
break;
|
||||
case JavaConstantKind.Class:
|
||||
pendingClassLiteral = pool.GetClassName(constantIndex);
|
||||
pendingString = null;
|
||||
break;
|
||||
default:
|
||||
pendingString = null;
|
||||
pendingClassLiteral = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleInvocation(
|
||||
JavaConstantPool pool,
|
||||
JavaMethod method,
|
||||
string segmentIdentifier,
|
||||
string className,
|
||||
int instructionOffset,
|
||||
int methodIndex,
|
||||
byte opcode,
|
||||
ref string? pendingString,
|
||||
ref string? pendingClassLiteral,
|
||||
ref bool sawCurrentThread,
|
||||
ref bool emittedTcclWarning,
|
||||
List<JavaReflectionEdge> edges,
|
||||
List<JavaReflectionWarning> warnings)
|
||||
{
|
||||
var methodRef = pool.GetMethodReference(methodIndex);
|
||||
|
||||
var owner = methodRef.OwnerInternalName;
|
||||
var name = methodRef.Name;
|
||||
var descriptor = methodRef.Descriptor;
|
||||
|
||||
var normalizedOwner = owner ?? string.Empty;
|
||||
var normalizedSource = NormalizeClassName(className) ?? className ?? string.Empty;
|
||||
|
||||
if (normalizedOwner == "java/lang/Class" && name == "forName")
|
||||
{
|
||||
var target = NormalizeClassName(pendingString);
|
||||
var confidence = pendingString is null ? JavaReflectionConfidence.Low : JavaReflectionConfidence.High;
|
||||
edges.Add(new JavaReflectionEdge(
|
||||
normalizedSource,
|
||||
segmentIdentifier,
|
||||
target,
|
||||
JavaReflectionReason.ClassForName,
|
||||
confidence,
|
||||
method.Name,
|
||||
method.Descriptor,
|
||||
instructionOffset,
|
||||
null));
|
||||
}
|
||||
else if (normalizedOwner == "java/lang/ClassLoader" && name == "loadClass")
|
||||
{
|
||||
var target = NormalizeClassName(pendingString);
|
||||
var confidence = pendingString is null ? JavaReflectionConfidence.Low : JavaReflectionConfidence.High;
|
||||
edges.Add(new JavaReflectionEdge(
|
||||
normalizedSource,
|
||||
segmentIdentifier,
|
||||
target,
|
||||
JavaReflectionReason.ClassLoaderLoadClass,
|
||||
confidence,
|
||||
method.Name,
|
||||
method.Descriptor,
|
||||
instructionOffset,
|
||||
null));
|
||||
}
|
||||
else if (normalizedOwner == "java/util/ServiceLoader" && name.StartsWith("load", StringComparison.Ordinal))
|
||||
{
|
||||
var target = NormalizeClassName(pendingClassLiteral);
|
||||
var confidence = pendingClassLiteral is null ? JavaReflectionConfidence.Low : JavaReflectionConfidence.High;
|
||||
edges.Add(new JavaReflectionEdge(
|
||||
normalizedSource,
|
||||
segmentIdentifier,
|
||||
target,
|
||||
JavaReflectionReason.ServiceLoaderLoad,
|
||||
confidence,
|
||||
method.Name,
|
||||
method.Descriptor,
|
||||
instructionOffset,
|
||||
null));
|
||||
}
|
||||
else if (normalizedOwner == "java/lang/ClassLoader" && (name == "getResource" || name == "getResourceAsStream" || name == "getResources"))
|
||||
{
|
||||
var target = pendingString;
|
||||
var confidence = pendingString is null ? JavaReflectionConfidence.Low : JavaReflectionConfidence.High;
|
||||
edges.Add(new JavaReflectionEdge(
|
||||
normalizedSource,
|
||||
segmentIdentifier,
|
||||
target,
|
||||
JavaReflectionReason.ResourceLookup,
|
||||
confidence,
|
||||
method.Name,
|
||||
method.Descriptor,
|
||||
instructionOffset,
|
||||
null));
|
||||
}
|
||||
else if (normalizedOwner == "java/lang/Thread" && name == "currentThread")
|
||||
{
|
||||
sawCurrentThread = true;
|
||||
}
|
||||
else if (normalizedOwner == "java/lang/Thread" && name == "getContextClassLoader")
|
||||
{
|
||||
if (sawCurrentThread && !emittedTcclWarning)
|
||||
{
|
||||
warnings.Add(new JavaReflectionWarning(
|
||||
normalizedSource,
|
||||
segmentIdentifier,
|
||||
"tccl",
|
||||
"Thread context class loader access detected.",
|
||||
method.Name,
|
||||
method.Descriptor));
|
||||
emittedTcclWarning = true;
|
||||
}
|
||||
}
|
||||
|
||||
pendingString = null;
|
||||
pendingClassLiteral = null;
|
||||
}
|
||||
|
||||
private static string? NormalizeClassName(string? internalName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(internalName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return internalName.Replace('/', '.');
|
||||
}
|
||||
|
||||
private static bool IsStoreInstruction(byte opcode)
|
||||
=> (opcode >= 0x3B && opcode <= 0x4E) || (opcode >= 0x4F && opcode <= 0x56) || (opcode >= 0x36 && opcode <= 0x3A);
|
||||
|
||||
private static bool IsStoreWithExplicitIndex(byte opcode)
|
||||
=> opcode >= 0x36 && opcode <= 0x3A;
|
||||
|
||||
private static bool IsLoadInstructionWithIndex(byte opcode)
|
||||
=> opcode >= 0x15 && opcode <= 0x19;
|
||||
|
||||
private static bool IsStackMutation(byte opcode)
|
||||
=> opcode is 0x57 or 0x58 or 0x59 or 0x5A or 0x5B or 0x5C or 0x5D or 0x5E or 0x5F;
|
||||
|
||||
private sealed class JavaClassFile
|
||||
{
|
||||
public JavaClassFile(string thisClassName, JavaConstantPool constantPool, ImmutableArray<JavaMethod> methods)
|
||||
{
|
||||
ThisClassName = thisClassName;
|
||||
ConstantPool = constantPool;
|
||||
Methods = methods;
|
||||
}
|
||||
|
||||
public string ThisClassName { get; }
|
||||
|
||||
public JavaConstantPool ConstantPool { get; }
|
||||
|
||||
public ImmutableArray<JavaMethod> Methods { get; }
|
||||
|
||||
public static JavaClassFile Parse(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var reader = new BigEndianReader(stream, leaveOpen: true);
|
||||
if (reader.ReadUInt32() != 0xCAFEBABE)
|
||||
{
|
||||
throw new InvalidDataException("Invalid Java class file magic header.");
|
||||
}
|
||||
|
||||
_ = reader.ReadUInt16(); // minor
|
||||
_ = reader.ReadUInt16(); // major
|
||||
|
||||
var constantPoolCount = reader.ReadUInt16();
|
||||
var pool = new JavaConstantPool(constantPoolCount);
|
||||
|
||||
var index = 1;
|
||||
while (index < constantPoolCount)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var tag = reader.ReadByte();
|
||||
switch ((JavaConstantTag)tag)
|
||||
{
|
||||
case JavaConstantTag.Utf8:
|
||||
{
|
||||
pool.Set(index, JavaConstantPoolEntry.Utf8(reader.ReadUtf8()));
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
case JavaConstantTag.Integer:
|
||||
reader.Skip(4);
|
||||
pool.Set(index, JavaConstantPoolEntry.Other(tag));
|
||||
index++;
|
||||
break;
|
||||
case JavaConstantTag.Float:
|
||||
reader.Skip(4);
|
||||
pool.Set(index, JavaConstantPoolEntry.Other(tag));
|
||||
index++;
|
||||
break;
|
||||
case JavaConstantTag.Long:
|
||||
case JavaConstantTag.Double:
|
||||
reader.Skip(8);
|
||||
pool.Set(index, JavaConstantPoolEntry.Other(tag));
|
||||
index += 2;
|
||||
break;
|
||||
case JavaConstantTag.Class:
|
||||
case JavaConstantTag.String:
|
||||
case JavaConstantTag.MethodType:
|
||||
pool.Set(index, JavaConstantPoolEntry.Indexed(tag, reader.ReadUInt16()));
|
||||
index++;
|
||||
break;
|
||||
case JavaConstantTag.Fieldref:
|
||||
case JavaConstantTag.Methodref:
|
||||
case JavaConstantTag.InterfaceMethodref:
|
||||
case JavaConstantTag.NameAndType:
|
||||
case JavaConstantTag.InvokeDynamic:
|
||||
pool.Set(index, JavaConstantPoolEntry.IndexedPair(tag, reader.ReadUInt16(), reader.ReadUInt16()));
|
||||
index++;
|
||||
break;
|
||||
case JavaConstantTag.MethodHandle:
|
||||
reader.Skip(1); // reference kind
|
||||
pool.Set(index, JavaConstantPoolEntry.Indexed(tag, reader.ReadUInt16()));
|
||||
index++;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDataException($"Unsupported constant pool tag {tag}.");
|
||||
}
|
||||
}
|
||||
|
||||
var accessFlags = reader.ReadUInt16();
|
||||
var thisClassIndex = reader.ReadUInt16();
|
||||
_ = reader.ReadUInt16(); // super
|
||||
|
||||
var interfacesCount = reader.ReadUInt16();
|
||||
reader.Skip(interfacesCount * 2);
|
||||
|
||||
var fieldsCount = reader.ReadUInt16();
|
||||
for (var i = 0; i < fieldsCount; i++)
|
||||
{
|
||||
SkipMember(reader);
|
||||
}
|
||||
|
||||
var methodsCount = reader.ReadUInt16();
|
||||
var methods = ImmutableArray.CreateBuilder<JavaMethod>(methodsCount);
|
||||
for (var i = 0; i < methodsCount; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_ = reader.ReadUInt16(); // method access flags
|
||||
var nameIndex = reader.ReadUInt16();
|
||||
var descriptorIndex = reader.ReadUInt16();
|
||||
var attributesCount = reader.ReadUInt16();
|
||||
|
||||
byte[]? code = null;
|
||||
|
||||
for (var attr = 0; attr < attributesCount; attr++)
|
||||
{
|
||||
var attributeNameIndex = reader.ReadUInt16();
|
||||
var attributeLength = reader.ReadUInt32();
|
||||
var attributeName = pool.GetUtf8(attributeNameIndex) ?? string.Empty;
|
||||
|
||||
if (attributeName == "Code")
|
||||
{
|
||||
var maxStack = reader.ReadUInt16();
|
||||
var maxLocals = reader.ReadUInt16();
|
||||
var codeLength = reader.ReadUInt32();
|
||||
code = reader.ReadBytes((int)codeLength);
|
||||
var exceptionTableLength = reader.ReadUInt16();
|
||||
reader.Skip(exceptionTableLength * 8);
|
||||
var codeAttributeCount = reader.ReadUInt16();
|
||||
for (var c = 0; c < codeAttributeCount; c++)
|
||||
{
|
||||
reader.Skip(2); // name index
|
||||
var len = reader.ReadUInt32();
|
||||
reader.Skip((int)len);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Skip((int)attributeLength);
|
||||
}
|
||||
}
|
||||
|
||||
if (code is not null)
|
||||
{
|
||||
var name = pool.GetUtf8(nameIndex) ?? string.Empty;
|
||||
var descriptor = pool.GetUtf8(descriptorIndex) ?? string.Empty;
|
||||
methods.Add(new JavaMethod(name, descriptor, code));
|
||||
}
|
||||
}
|
||||
|
||||
var classAttributesCount = reader.ReadUInt16();
|
||||
for (var a = 0; a < classAttributesCount; a++)
|
||||
{
|
||||
reader.Skip(2);
|
||||
var len = reader.ReadUInt32();
|
||||
reader.Skip((int)len);
|
||||
}
|
||||
|
||||
var thisClassName = pool.GetClassName(thisClassIndex) ?? string.Empty;
|
||||
return new JavaClassFile(thisClassName, pool, methods.ToImmutable());
|
||||
}
|
||||
|
||||
private static void SkipMember(BigEndianReader reader)
|
||||
{
|
||||
reader.Skip(6); // access, name, descriptor
|
||||
var attributeCount = reader.ReadUInt16();
|
||||
for (var i = 0; i < attributeCount; i++)
|
||||
{
|
||||
reader.Skip(2);
|
||||
var len = reader.ReadUInt32();
|
||||
reader.Skip((int)len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class JavaMethod
|
||||
{
|
||||
public JavaMethod(string name, string descriptor, byte[] code)
|
||||
{
|
||||
Name = name;
|
||||
Descriptor = descriptor;
|
||||
Code = code;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Descriptor { get; }
|
||||
|
||||
public byte[] Code { get; }
|
||||
}
|
||||
|
||||
private sealed class JavaConstantPool
|
||||
{
|
||||
private readonly JavaConstantPoolEntry?[] _entries;
|
||||
|
||||
public JavaConstantPool(int count)
|
||||
{
|
||||
_entries = new JavaConstantPoolEntry?[count];
|
||||
}
|
||||
|
||||
public void Set(int index, JavaConstantPoolEntry entry)
|
||||
{
|
||||
_entries[index] = entry;
|
||||
}
|
||||
|
||||
public JavaConstantKind GetConstantKind(int index)
|
||||
{
|
||||
var entry = _entries[index];
|
||||
return entry?.Kind ?? JavaConstantKind.Other;
|
||||
}
|
||||
|
||||
public string? GetUtf8(int index)
|
||||
{
|
||||
if (index <= 0 || index >= _entries.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _entries[index] is JavaConstantPoolEntry.Utf8Entry utf8 ? utf8.Value : null;
|
||||
}
|
||||
|
||||
public string? GetString(int index)
|
||||
{
|
||||
if (_entries[index] is JavaConstantPoolEntry.IndexedEntry { Kind: JavaConstantKind.String, Index: var utf8Index })
|
||||
{
|
||||
return GetUtf8(utf8Index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string? GetClassName(int index)
|
||||
{
|
||||
if (_entries[index] is JavaConstantPoolEntry.IndexedEntry { Kind: JavaConstantKind.Class, Index: var nameIndex })
|
||||
{
|
||||
return GetUtf8(nameIndex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public JavaMethodReference GetMethodReference(int index)
|
||||
{
|
||||
if (_entries[index] is not JavaConstantPoolEntry.IndexedPairEntry pair || pair.Kind is not (JavaConstantKind.Methodref or JavaConstantKind.InterfaceMethodref))
|
||||
{
|
||||
throw new InvalidDataException($"Constant pool entry {index} is not a method reference.");
|
||||
}
|
||||
|
||||
var owner = GetClassName(pair.FirstIndex) ?? string.Empty;
|
||||
var nameAndType = _entries[pair.SecondIndex] as JavaConstantPoolEntry.IndexedPairEntry;
|
||||
if (nameAndType is null || nameAndType.Kind != JavaConstantKind.NameAndType)
|
||||
{
|
||||
throw new InvalidDataException("Invalid NameAndType entry for method reference.");
|
||||
}
|
||||
|
||||
var name = GetUtf8(nameAndType.FirstIndex) ?? string.Empty;
|
||||
var descriptor = GetUtf8(nameAndType.SecondIndex) ?? string.Empty;
|
||||
return new JavaMethodReference(owner, name, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct JavaMethodReference(string OwnerInternalName, string Name, string Descriptor);
|
||||
|
||||
private abstract record JavaConstantPoolEntry(JavaConstantKind Kind)
|
||||
{
|
||||
public sealed record Utf8Entry(string Value) : JavaConstantPoolEntry(JavaConstantKind.Utf8);
|
||||
|
||||
public sealed record IndexedEntry(JavaConstantKind Kind, ushort Index) : JavaConstantPoolEntry(Kind);
|
||||
|
||||
public sealed record IndexedPairEntry(JavaConstantKind Kind, ushort FirstIndex, ushort SecondIndex) : JavaConstantPoolEntry(Kind);
|
||||
|
||||
public sealed record OtherEntry(byte Tag) : JavaConstantPoolEntry(JavaConstantKind.Other);
|
||||
|
||||
public static JavaConstantPoolEntry Utf8(string value) => new Utf8Entry(value);
|
||||
|
||||
public static JavaConstantPoolEntry Indexed(byte tag, ushort index)
|
||||
=> new IndexedEntry(ToKind(tag), index);
|
||||
|
||||
public static JavaConstantPoolEntry IndexedPair(byte tag, ushort first, ushort second)
|
||||
=> new IndexedPairEntry(ToKind(tag), first, second);
|
||||
|
||||
public static JavaConstantPoolEntry Other(byte tag) => new OtherEntry(tag);
|
||||
|
||||
private static JavaConstantKind ToKind(byte tag)
|
||||
=> tag switch
|
||||
{
|
||||
7 => JavaConstantKind.Class,
|
||||
8 => JavaConstantKind.String,
|
||||
9 => JavaConstantKind.Fieldref,
|
||||
10 => JavaConstantKind.Methodref,
|
||||
11 => JavaConstantKind.InterfaceMethodref,
|
||||
12 => JavaConstantKind.NameAndType,
|
||||
15 => JavaConstantKind.MethodHandle,
|
||||
16 => JavaConstantKind.MethodType,
|
||||
18 => JavaConstantKind.InvokeDynamic,
|
||||
_ => JavaConstantKind.Other,
|
||||
};
|
||||
}
|
||||
|
||||
private enum JavaConstantKind
|
||||
{
|
||||
Utf8,
|
||||
Integer,
|
||||
Float,
|
||||
Long,
|
||||
Double,
|
||||
Class,
|
||||
String,
|
||||
Fieldref,
|
||||
Methodref,
|
||||
InterfaceMethodref,
|
||||
NameAndType,
|
||||
MethodHandle,
|
||||
MethodType,
|
||||
InvokeDynamic,
|
||||
Other,
|
||||
}
|
||||
|
||||
private enum JavaConstantTag : byte
|
||||
{
|
||||
Utf8 = 1,
|
||||
Integer = 3,
|
||||
Float = 4,
|
||||
Long = 5,
|
||||
Double = 6,
|
||||
Class = 7,
|
||||
String = 8,
|
||||
Fieldref = 9,
|
||||
Methodref = 10,
|
||||
InterfaceMethodref = 11,
|
||||
NameAndType = 12,
|
||||
MethodHandle = 15,
|
||||
MethodType = 16,
|
||||
InvokeDynamic = 18,
|
||||
}
|
||||
|
||||
private sealed class BigEndianReader
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly BinaryReader _reader;
|
||||
|
||||
public BigEndianReader(Stream stream, bool leaveOpen)
|
||||
{
|
||||
_stream = stream;
|
||||
_reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen);
|
||||
}
|
||||
|
||||
public ushort ReadUInt16()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[2];
|
||||
Fill(buffer);
|
||||
return BinaryPrimitives.ReadUInt16BigEndian(buffer);
|
||||
}
|
||||
|
||||
public uint ReadUInt32()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
Fill(buffer);
|
||||
return BinaryPrimitives.ReadUInt32BigEndian(buffer);
|
||||
}
|
||||
|
||||
public int ReadInt32()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
Fill(buffer);
|
||||
return BinaryPrimitives.ReadInt32BigEndian(buffer);
|
||||
}
|
||||
|
||||
public byte ReadByte() => _reader.ReadByte();
|
||||
|
||||
public string ReadUtf8()
|
||||
{
|
||||
var length = ReadUInt16();
|
||||
var bytes = ReadBytes(length);
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(int count)
|
||||
{
|
||||
var bytes = _reader.ReadBytes(count);
|
||||
if (bytes.Length != count)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void Skip(int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer = new byte[Math.Min(count, 4096)];
|
||||
var remaining = count;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
var read = _stream.Read(buffer, 0, Math.Min(buffer.Length, remaining));
|
||||
if (read == 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
remaining -= read;
|
||||
}
|
||||
}
|
||||
|
||||
private void Fill(Span<byte> buffer)
|
||||
{
|
||||
var read = _stream.Read(buffer);
|
||||
if (read != buffer.Length)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user