Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ public class AttributeInfo {
public const string StackMapTable = "StackMapTable";
public const string RuntimeVisibleAnnotations = "RuntimeVisibleAnnotations";
public const string RuntimeInvisibleAnnotations = "RuntimeInvisibleAnnotations";
public const string RuntimeVisibleParameterAnnotations = "RuntimeVisibleParameterAnnotations";
public const string RuntimeInvisibleParameterAnnotations = "RuntimeInvisibleParameterAnnotations";
public const string RuntimeVisibleTypeAnnotations = "RuntimeVisibleTypeAnnotations";
public const string RuntimeInvisibleTypeAnnotations = "RuntimeInvisibleTypeAnnotations";

ushort nameIndex;

Expand Down Expand Up @@ -85,6 +88,10 @@ public string Name {
{ typeof (ModulePackagesAttribute), ModulePackages },
{ typeof (RuntimeVisibleAnnotationsAttribute), RuntimeVisibleAnnotations },
{ typeof (RuntimeInvisibleAnnotationsAttribute), RuntimeInvisibleAnnotations },
{ typeof (RuntimeVisibleParameterAnnotationsAttribute), RuntimeVisibleParameterAnnotations },
{ typeof (RuntimeInvisibleParameterAnnotationsAttribute), RuntimeInvisibleParameterAnnotations },
{ typeof (RuntimeVisibleTypeAnnotationsAttribute), RuntimeVisibleTypeAnnotations },
{ typeof (RuntimeInvisibleTypeAnnotationsAttribute), RuntimeInvisibleTypeAnnotations },
{ typeof (SignatureAttribute), Signature },
{ typeof (SourceFileAttribute), SourceFile },
{ typeof (StackMapTableAttribute), StackMapTable },
Expand Down Expand Up @@ -123,7 +130,10 @@ static AttributeInfo CreateAttribute (string name, ConstantPool constantPool, us
case ModulePackages: return new ModulePackagesAttribute (constantPool, nameIndex, stream);
case RuntimeVisibleAnnotations: return new RuntimeVisibleAnnotationsAttribute (constantPool, nameIndex, stream);
case RuntimeInvisibleAnnotations: return new RuntimeInvisibleAnnotationsAttribute (constantPool, nameIndex, stream);
case RuntimeVisibleParameterAnnotations: return new RuntimeVisibleParameterAnnotationsAttribute (constantPool, nameIndex, stream);
case RuntimeInvisibleParameterAnnotations: return new RuntimeInvisibleParameterAnnotationsAttribute (constantPool, nameIndex, stream);
case RuntimeVisibleTypeAnnotations: return new RuntimeVisibleTypeAnnotationsAttribute (constantPool, nameIndex, stream);
case RuntimeInvisibleTypeAnnotations: return new RuntimeInvisibleTypeAnnotationsAttribute (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);
Expand Down Expand Up @@ -671,6 +681,31 @@ public override string ToString ()
}


// https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.18
public sealed class RuntimeVisibleParameterAnnotationsAttribute : AttributeInfo
{
public IList<ParameterAnnotation> Annotations { get; } = new List<ParameterAnnotation> ();

public RuntimeVisibleParameterAnnotationsAttribute (ConstantPool constantPool, ushort nameIndex, Stream stream)
: base (constantPool, nameIndex, stream)
{
var length = stream.ReadNetworkUInt32 ();
var param_count = stream.ReadNetworkByte ();

for (var i = 0; i < param_count; ++i) {
var a = new ParameterAnnotation (constantPool, stream, i);
Annotations.Add (a);
}
}

public override string ToString ()
{
var annotations = string.Join (", ", Annotations.Select (v => v.ToString ()));
return $"RuntimeVisibleParameterAnnotationsAttribute({annotations})";
}
}


// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.19
public sealed class RuntimeInvisibleParameterAnnotationsAttribute : AttributeInfo
{
Expand All @@ -695,6 +730,52 @@ public override string ToString ()
}
}

// https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.20
public sealed class RuntimeVisibleTypeAnnotationsAttribute : AttributeInfo
{
public IList<TypeAnnotation> Annotations { get; } = new List<TypeAnnotation> ();

public RuntimeVisibleTypeAnnotationsAttribute (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 TypeAnnotation (constantPool, stream));
}
}

public override string ToString ()
{
var annotations = string.Join (", ", Annotations.Select (v => v.ToString ()));
return $"RuntimeVisibleTypeAnnotations({annotations})";
}
}

// https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.21
public sealed class RuntimeInvisibleTypeAnnotationsAttribute : AttributeInfo
{
public IList<TypeAnnotation> Annotations { get; } = new List<TypeAnnotation> ();

public RuntimeInvisibleTypeAnnotationsAttribute (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 TypeAnnotation (constantPool, stream));
}
}

public override string ToString ()
{
var annotations = string.Join (", ", Annotations.Select (v => v.ToString ()));
return $"RuntimeInvisibleTypeAnnotations({annotations})";
}
}

// http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.9
public sealed class SignatureAttribute : AttributeInfo {

Expand Down
12 changes: 12 additions & 0 deletions src/Xamarin.Android.Tools.Bytecode/ClassFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ public string PackageName {

public string FullJniName => "L" + ThisClass.Name.Value + ";";

// `package-info.class` is a synthetic class that carries
// package-level annotations (e.g. JSpecify's `@NullMarked`).
// Its simple name is `package-info`.
public bool IsPackageInfo {
get {
var name = ThisClass.Name.Value;
var slash = name.LastIndexOf ('/');
var simple = slash < 0 ? name : name.Substring (slash + 1);
return simple == "package-info";
}
}

public string? SourceFileName {
get {
var sourceFile = Attributes.Get<SourceFileAttribute> ();
Expand Down
17 changes: 15 additions & 2 deletions src/Xamarin.Android.Tools.Bytecode/ClassPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ public ReadOnlyDictionary<string, List<ClassFile>> GetPackages ()
x => classFiles.Where (p => p.PackageName == x).ToList ()));
}

// Returns the `package-info.class` for the given package, if one
// is present on this ClassPath. Used by `XmlClassDeclarationBuilder`
// to honor package-level JSpecify `@NullMarked` annotations.
public ClassFile? GetPackageInfo (string packageName)
{
foreach (var c in classFiles) {
if (c.IsPackageInfo && c.PackageName == packageName)
return c;
}
return null;
}

public static bool IsJarFile (string jarFile)
{
if (jarFile == null)
Expand Down Expand Up @@ -375,8 +387,9 @@ public XElement ToXElement ()
.Select (p => new XElement ("package",
new XAttribute ("name", p),
new XAttribute ("jni-name", p.Replace ('.', '/')),
packagesDictionary [p].OrderBy (c => c.ThisClass.Name.Value, StringComparer.OrdinalIgnoreCase)
.Select (c => new XmlClassDeclarationBuilder (c).ToXElement ()))));
packagesDictionary [p].Where (c => !c.IsPackageInfo)
.OrderBy (c => c.ThisClass.Name.Value, StringComparer.OrdinalIgnoreCase)
.Select (c => new XmlClassDeclarationBuilder (c, GetPackageInfo (p)).ToXElement ()))));
Comment on lines 387 to +392
FixupParametersFromDocs (api);
return api;
}
Expand Down
129 changes: 129 additions & 0 deletions src/Xamarin.Android.Tools.Bytecode/TypeAnnotation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.IO;

namespace Xamarin.Android.Tools.Bytecode
{
// https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.20
public enum TypeAnnotationTargetType : byte
{
ClassTypeParameter = 0x00,
MethodTypeParameter = 0x01,
ClassExtends = 0x10,
ClassTypeParameterBound = 0x11,
MethodTypeParameterBound = 0x12,
Field = 0x13,
MethodReturn = 0x14,
MethodReceiver = 0x15,
MethodFormalParameter = 0x16,
Throws = 0x17,
LocalVariable = 0x40,
ResourceVariable = 0x41,
ExceptionParameter = 0x42,
Instanceof = 0x43,
New = 0x44,
ConstructorReference = 0x45,
MethodReference = 0x46,
Cast = 0x47,
ConstructorInvocationTypeArgument = 0x48,
MethodInvocationTypeArgument = 0x49,
ConstructorReferenceTypeArgument = 0x4A,
MethodReferenceTypeArgument = 0x4B,
}

// https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.20
public sealed class TypeAnnotation
{
public TypeAnnotationTargetType TargetType { get; }

// `formal_parameter_index` for MethodFormalParameter; otherwise 0.
public int FormalParameterIndex { get; }

// `path_length` from `type_path`; we don't retain the individual
// path entries — see AppliesToTopLevelType.
public int TypePathLength { get; }

public Annotation Annotation { get; }

// JSpecify-style nullness annotations only apply to the top-level
// type when there is no `type_path`. Annotations with a non-empty
// path describe inner types (e.g. `Map<@Nullable K, V>`) which the
// API XML schema cannot currently represent.
public bool AppliesToTopLevelType => TypePathLength == 0;

public TypeAnnotation (ConstantPool constantPool, Stream stream)
{
TargetType = (TypeAnnotationTargetType) stream.ReadNetworkByte ();

// target_info — size depends on target_type.
switch (TargetType) {
case TypeAnnotationTargetType.ClassTypeParameter:
case TypeAnnotationTargetType.MethodTypeParameter:
stream.ReadNetworkByte (); // type_parameter_index
break;
case TypeAnnotationTargetType.ClassExtends:
stream.ReadNetworkUInt16 (); // supertype_index
break;
case TypeAnnotationTargetType.ClassTypeParameterBound:
case TypeAnnotationTargetType.MethodTypeParameterBound:
stream.ReadNetworkByte (); // type_parameter_index
stream.ReadNetworkByte (); // bound_index
break;
case TypeAnnotationTargetType.Field:
case TypeAnnotationTargetType.MethodReturn:
case TypeAnnotationTargetType.MethodReceiver:
// empty_target — no bytes
break;
case TypeAnnotationTargetType.MethodFormalParameter:
FormalParameterIndex = stream.ReadNetworkByte ();
break;
case TypeAnnotationTargetType.Throws:
stream.ReadNetworkUInt16 (); // throws_type_index
break;
case TypeAnnotationTargetType.LocalVariable:
case TypeAnnotationTargetType.ResourceVariable: {
var table_length = stream.ReadNetworkUInt16 ();
for (int i = 0; i < table_length; ++i) {
stream.ReadNetworkUInt16 (); // start_pc
stream.ReadNetworkUInt16 (); // length
stream.ReadNetworkUInt16 (); // index
}
break;
}
case TypeAnnotationTargetType.ExceptionParameter:
stream.ReadNetworkUInt16 (); // exception_table_index
break;
case TypeAnnotationTargetType.Instanceof:
case TypeAnnotationTargetType.New:
case TypeAnnotationTargetType.ConstructorReference:
case TypeAnnotationTargetType.MethodReference:
stream.ReadNetworkUInt16 (); // offset
break;
case TypeAnnotationTargetType.Cast:
case TypeAnnotationTargetType.ConstructorInvocationTypeArgument:
case TypeAnnotationTargetType.MethodInvocationTypeArgument:
case TypeAnnotationTargetType.ConstructorReferenceTypeArgument:
case TypeAnnotationTargetType.MethodReferenceTypeArgument:
stream.ReadNetworkUInt16 (); // offset
stream.ReadNetworkByte (); // type_argument_index
break;
default:
throw new NotSupportedException ($"Unknown type_annotation target_type: 0x{(byte)TargetType:X2}");
}

// type_path: u1 path_length, then path_length * { u1 type_path_kind; u1 type_argument_index; }
TypePathLength = stream.ReadNetworkByte ();
for (int i = 0; i < TypePathLength; ++i) {
stream.ReadNetworkByte (); // type_path_kind
stream.ReadNetworkByte (); // type_argument_index
}

// The remaining bytes match the regular `annotation` structure.
Annotation = new Annotation (constantPool, stream);
}

public override string ToString ()
{
return $"TypeAnnotation({TargetType}, paramIndex={FormalParameterIndex}, pathLength={TypePathLength}, {Annotation})";
}
}
}
Loading