diff --git a/src/Xamarin.Android.Tools.Bytecode/Annotation.cs b/src/Xamarin.Android.Tools.Bytecode/Annotation.cs new file mode 100644 index 000000000..968aac350 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Annotation.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Xamarin.Android.Tools.Bytecode +{ + public sealed class Annotation + { + public ConstantPool ConstantPool { get; } + + ushort typeIndex; + public string Type => ((ConstantPoolUtf8Item) ConstantPool [typeIndex]).Value; + + public IList> + Values { get; } = new List> (); + + public Annotation (ConstantPool constantPool, Stream stream) + { + ConstantPool = constantPool; + typeIndex = stream.ReadNetworkUInt16 (); + + var count = stream.ReadNetworkUInt16 (); + for (int i = 0; i < count; ++i) { + var elementNameIndex = stream.ReadNetworkUInt16 (); + var elementName = ((ConstantPoolUtf8Item) ConstantPool [elementNameIndex]).Value; + var elementValue = AnnotationElementValue.Create (constantPool, stream); + Values.Add (new KeyValuePair (elementName, elementValue)); + } + } + + public override string ToString () + { + var values = new StringBuilder ("{"); + if (Values.Count > 0) { + Append (Values [0]); + } + for (int i = 1; i < Values.Count; ++i) { + values.Append (", "); + Append (Values [i]); + } + values.Append ("}"); + return $"Annotation('{Type}', {values})"; + + void Append (KeyValuePair value) + { + values.Append (value.Key).Append (": "); + values.Append (value.Value); + } + } + } +} diff --git a/src/Xamarin.Android.Tools.Bytecode/AnnotationElementValue.cs b/src/Xamarin.Android.Tools.Bytecode/AnnotationElementValue.cs new file mode 100644 index 000000000..b2904faca --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/AnnotationElementValue.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamarin.Android.Tools.Bytecode +{ + // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.16.1 + public abstract class AnnotationElementValue + { + public virtual string ToEncodedString () => ToString (); + + public static AnnotationElementValue Create (ConstantPool constantPool, Stream stream) + { + var tag = stream.ReadNetworkByte (); + + switch (tag) { + case (byte) 'e': { + var typeNameIndex = stream.ReadNetworkUInt16 (); + var constNameIndex = stream.ReadNetworkUInt16 (); + + var typeName = ((ConstantPoolUtf8Item) constantPool [typeNameIndex]).Value; + var constName = ((ConstantPoolUtf8Item) constantPool [constNameIndex]).Value; + + return new AnnotationElementEnum { TypeName = typeName, ConstantName = constName }; + } + case (byte) 'c': { + var classInfoIndex = stream.ReadNetworkUInt16 (); + + return new AnnotationElementClassInfo { ClassInfo = ((ConstantPoolUtf8Item) constantPool [classInfoIndex]).Value }; + } + case (byte) '@': { + return new AnnotationElementAnnotation { Annotation = new Annotation (constantPool, stream) }; + } + case (byte) '[': { + var numValues = stream.ReadNetworkUInt16 (); + + var values = new List (); + + for (var i = 0; i < numValues; i++) + values.Add (Create (constantPool, stream)); + + return new AnnotationElementArray { Values = values.ToArray () }; + } + case (byte) 'B': + case (byte) 'C': + case (byte) 'D': + case (byte) 'F': + case (byte) 'I': + case (byte) 'J': + case (byte) 'S': + case (byte) 's': + case (byte) 'Z': { + var constValueIndex = stream.ReadNetworkUInt16 (); + var poolItem = constantPool [constValueIndex]; + var value = poolItem.ToString (); + + if (poolItem is ConstantPoolDoubleItem d) + value = d.Value.ToString (); + else if (poolItem is ConstantPoolFloatItem f) + value = f.Value.ToString (); + else if (poolItem is ConstantPoolIntegerItem i) + value = i.Value.ToString (); + else if (poolItem is ConstantPoolLongItem l) + value = l.Value.ToString (); + else if (poolItem is ConstantPoolStringItem s) + return new AnnotationStringElementConstant { Value = s.StringData.Value.ToString () }; + else if (poolItem is ConstantPoolUtf8Item u) + return new AnnotationStringElementConstant { Value = u.Value.ToString () }; + + return new AnnotationElementConstant { Value = value }; + } + } + + return null; + } + } + + public class AnnotationElementEnum : AnnotationElementValue + { + public string TypeName { get; set; } + public string ConstantName { get; set; } + + public override string ToString () => $"Enum({TypeName}.{ConstantName})"; + } + + public class AnnotationElementClassInfo : AnnotationElementValue + { + public string ClassInfo { get; set; } + + public override string ToString () => ClassInfo; + } + + public class AnnotationElementAnnotation : AnnotationElementValue + { + public Annotation Annotation { get; set; } + + public override string ToString () => Annotation.ToString (); + } + + public class AnnotationElementArray : AnnotationElementValue + { + public AnnotationElementValue[] Values { get; set; } + + public override string ToString () => $"[{string.Join (", ", Values.Select (v => v.ToString ()))}]"; + + public override string ToEncodedString () => $"[{string.Join (", ", Values.Select (v => v.ToEncodedString ()))}]"; + } + + public class AnnotationElementConstant : AnnotationElementValue + { + public string Value { get; set; } + + public override string ToString () => Value; + } + + public class AnnotationStringElementConstant : AnnotationElementConstant + { + public override string ToString () => $"\"{Value}\""; + + public override string ToEncodedString () + { + return $"\"{Convert.ToBase64String (Encoding.UTF8.GetBytes (Value))}\""; + } + } +} diff --git a/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs b/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs index 4af73a9e5..c2bbbd832 100644 --- a/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs +++ b/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs @@ -47,6 +47,8 @@ public class AttributeInfo { public const string Signature = "Signature"; public const string SourceFile = "SourceFile"; public const string StackMapTable = "StackMapTable"; + public const string RuntimeVisibleAnnotations = "RuntimeVisibleAnnotations"; + public const string RuntimeInvisibleAnnotations = "RuntimeInvisibleAnnotations"; ushort nameIndex; @@ -76,6 +78,8 @@ public string Name { { typeof (InnerClassesAttribute), InnerClasses }, { typeof (LocalVariableTableAttribute), LocalVariableTable }, { typeof (MethodParametersAttribute), MethodParameters }, + { typeof (RuntimeVisibleAnnotationsAttribute), RuntimeVisibleAnnotations }, + { typeof (RuntimeInvisibleAnnotationsAttribute), RuntimeInvisibleAnnotations }, { typeof (SignatureAttribute), Signature }, { typeof (SourceFileAttribute), SourceFile }, { typeof (StackMapTableAttribute), StackMapTable }, @@ -109,6 +113,8 @@ static AttributeInfo CreateAttribute (string name, ConstantPool constantPool, us case InnerClasses: return new InnerClassesAttribute (constantPool, nameIndex, stream); case LocalVariableTable: return new LocalVariableTableAttribute (constantPool, nameIndex, stream); case MethodParameters: return new MethodParametersAttribute (constantPool, nameIndex, stream); + case RuntimeVisibleAnnotations: return new RuntimeVisibleAnnotationsAttribute (constantPool, nameIndex, stream); + case RuntimeInvisibleAnnotations: return new RuntimeInvisibleAnnotationsAttribute (constantPool, nameIndex, stream); case Signature: return new SignatureAttribute (constantPool, nameIndex, stream); case SourceFile: return new SourceFileAttribute (constantPool, nameIndex, stream); case StackMapTable: return new StackMapTableAttribute (constantPool, nameIndex, stream); @@ -493,6 +499,54 @@ public override string ToString () } } + // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.16 + public sealed class RuntimeVisibleAnnotationsAttribute : AttributeInfo + { + + public IList Annotations { get; } = new List (); + + public RuntimeVisibleAnnotationsAttribute (ConstantPool constantPool, ushort nameIndex, Stream stream) + : base (constantPool, nameIndex, stream) + { + var length = stream.ReadNetworkUInt32 (); + var count = stream.ReadNetworkUInt16 (); + + for (int i = 0; i < count; ++i) { + Annotations.Add (new Annotation (constantPool, stream)); + } + } + + public override string ToString () + { + var annotations = string.Join (", ", Annotations.Select (v => v.ToString ())); + return $"RuntimeVisibleAnnotations({annotations})"; + } + } + + // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.17 + public sealed class RuntimeInvisibleAnnotationsAttribute : AttributeInfo + { + public IList Annotations { get; } = new List (); + + public RuntimeInvisibleAnnotationsAttribute (ConstantPool constantPool, ushort nameIndex, Stream stream) + : base (constantPool, nameIndex, stream) + { + var length = stream.ReadNetworkUInt32 (); + var count = stream.ReadNetworkUInt16 (); + + for (int i = 0; i < count; ++i) { + var a = new Annotation (constantPool, stream); + Annotations.Add (a); + } + } + + public override string ToString () + { + var annotations = string.Join (", ", Annotations.Select (v => v.ToString ())); + return $"RuntimeVisibleAnnotations({annotations})"; + } + } + // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.9 public sealed class SignatureAttribute : AttributeInfo { diff --git a/src/Xamarin.Android.Tools.Bytecode/Xamarin.Android.Tools.Bytecode.csproj b/src/Xamarin.Android.Tools.Bytecode/Xamarin.Android.Tools.Bytecode.csproj index e1feb77d6..5808c2471 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Xamarin.Android.Tools.Bytecode.csproj +++ b/src/Xamarin.Android.Tools.Bytecode/Xamarin.Android.Tools.Bytecode.csproj @@ -37,6 +37,8 @@ + +