Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[XAT.Bytecode] Add support for reading NotNull metadata into api.xml. #526

Merged
merged 1 commit into from
Dec 2, 2019
Merged
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
5 changes: 4 additions & 1 deletion src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApi.XmlModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ public JavaField (JavaType parent)
: base (parent)
{
}


public bool NotNull { get; set; }
public bool Transient { get; set; }
public string Type { get; set; }
public string TypeGeneric { get; set; }
Expand Down Expand Up @@ -248,6 +249,7 @@ public JavaMethod (JavaType parent)
public bool Abstract { get; set; }
public bool Native { get; set; }
public string Return { get; set; }
public bool ReturnNotNull { get; set; }
public bool Synchronized { get; set; }

// Content of this value is not stable.
Expand All @@ -268,6 +270,7 @@ public JavaParameter (JavaMethodBase parent)
public string Name { get; set; }
public string Type { get; set; }
public string JniType { get; set; }
public bool NotNull { get; set; }

// Content of this value is not stable.
public override string ToString ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,13 @@ static void Save (this JavaField field, XmlWriter writer)
null,
null,
null,
null);
null,
field.NotNull);
}

static void Save (this JavaConstructor ctor, XmlWriter writer)
{
SaveCommon (ctor, writer, "constructor", null, null, null, null, null, ctor.Type ?? ctor.Parent.FullName, null, null, null, ctor.TypeParameters, ctor.Parameters, ctor.Exceptions, ctor.ExtendedBridge, ctor.ExtendedJniReturn, ctor.ExtendedSynthetic);
SaveCommon (ctor, writer, "constructor", null, null, null, null, null, ctor.Type ?? ctor.Parent.FullName, null, null, null, ctor.TypeParameters, ctor.Parameters, ctor.Exceptions, ctor.ExtendedBridge, ctor.ExtendedJniReturn, ctor.ExtendedSynthetic, null);
}

static void Save (this JavaMethod method, XmlWriter writer)
Expand Down Expand Up @@ -217,7 +218,8 @@ static void Save (this JavaMethod method, XmlWriter writer)
method.Exceptions,
method.ExtendedBridge,
method.ExtendedJniReturn,
method.ExtendedSynthetic);
method.ExtendedSynthetic,
method.ReturnNotNull);
}

static void SaveCommon (this JavaMember m, XmlWriter writer, string elementName,
Expand All @@ -227,7 +229,7 @@ static void SaveCommon (this JavaMember m, XmlWriter writer, string elementName,
JavaTypeParameters typeParameters,
IEnumerable<JavaParameter> parameters,
IEnumerable<JavaException> exceptions,
bool? extBridge, string jniReturn, bool? extSynthetic)
bool? extBridge, string jniReturn, bool? extSynthetic, bool? notNull)
{
// If any of the parameters contain reference to non-public type, it cannot be generated.
if (parameters != null && parameters.Any (p => p.ResolvedType.ReferencedType != null && string.IsNullOrEmpty (p.ResolvedType.ReferencedType.Visibility)))
Expand All @@ -248,6 +250,8 @@ static void SaveCommon (this JavaMember m, XmlWriter writer, string elementName,
writer.WriteAttributeString ("return", ret);
if (jniReturn != null)
writer.WriteAttributeString ("jni-return", jniReturn);
if (notNull.GetValueOrDefault ())
writer.WriteAttributeString (m is JavaField ? "not-null" : "return-not-null", "true");
writer.WriteAttributeString ("static", XmlConvert.ToString (m.Static));
if (sync != null)
writer.WriteAttributeString ("synchronized", sync);
Expand Down Expand Up @@ -276,6 +280,9 @@ static void SaveCommon (this JavaMember m, XmlWriter writer, string elementName,
if (!string.IsNullOrEmpty (p.JniType)) {
writer.WriteAttributeString ("jni-type", p.JniType);
}
if (p.NotNull == true) {
writer.WriteAttributeString ("not-null", "true");
}
writer.WriteString ("\n ");
writer.WriteFullEndElement ();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,11 @@ public static void Load (this JavaField field, XmlReader reader)
{
field.LoadMemberAttributes (reader);
field.Transient = XmlConvert.ToBoolean (XmlUtil.GetRequiredAttribute (reader, "transient"));
field.Volatile = XmlConvert.ToBoolean (XmlUtil.GetRequiredAttribute (reader, "transient"));
field.Volatile = XmlConvert.ToBoolean (XmlUtil.GetRequiredAttribute (reader, "volatile"));
field.Type = XmlUtil.GetRequiredAttribute (reader, "type");
field.TypeGeneric = XmlUtil.GetRequiredAttribute (reader, "type-generic-aware");
field.Value = reader.GetAttribute ("value");
field.NotNull = reader.GetAttribute ("not-null") == "true";

reader.Skip ();
}
Expand Down Expand Up @@ -277,9 +278,10 @@ public static void Load (this JavaMethod method, XmlReader reader)
method.Abstract = XmlConvert.ToBoolean (XmlUtil.GetRequiredAttribute (reader, "abstract"));
method.Native = XmlConvert.ToBoolean (XmlUtil.GetRequiredAttribute (reader, "native"));
method.Return = XmlUtil.GetRequiredAttribute (reader, "return");
method.ReturnNotNull = reader.GetAttribute ("return-not-null") == "true";
method.Synchronized = XmlConvert.ToBoolean (XmlUtil.GetRequiredAttribute (reader, "synchronized"));
XmlUtil.CheckExtraneousAttributes ("method", reader, "deprecated", "final", "name", "static", "visibility", "jni-signature", "jni-return", "synthetic", "bridge",
"abstract", "native", "return", "synchronized");
"abstract", "native", "return", "synchronized", "return-not-null");
method.LoadMethodBase ("method", reader);
}

Expand All @@ -288,6 +290,7 @@ internal static void Load (this JavaParameter p, XmlReader reader)
p.Name = XmlUtil.GetRequiredAttribute (reader, "name");
p.Type = XmlUtil.GetRequiredAttribute (reader, "type");
p.JniType = reader.GetAttribute ("jni-type");
p.NotNull = reader.GetAttribute ("not-null") == "true";
reader.Skip ();
}

Expand Down
28 changes: 28 additions & 0 deletions src/Xamarin.Android.Tools.Bytecode/Annotation.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Xamarin.Android.Tools.Bytecode
Expand Down Expand Up @@ -48,4 +49,31 @@ void Append (KeyValuePair<string, AnnotationElementValue> value)
}
}
}

public sealed class ParameterAnnotation
{
public int ParameterIndex { get; }
public IList<Annotation> Annotations { get; } = new List<Annotation> ();
public ConstantPool ConstantPool { get; }

public ParameterAnnotation (ConstantPool constantPool, Stream stream, int index)
{
ConstantPool = constantPool;

ParameterIndex = index;

var ann_count = stream.ReadNetworkUInt16 ();

for (var i = 0; i < ann_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 $"Parameter{ParameterIndex}({annotations})";
}
}
}
29 changes: 28 additions & 1 deletion src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class AttributeInfo {
public const string StackMapTable = "StackMapTable";
public const string RuntimeVisibleAnnotations = "RuntimeVisibleAnnotations";
public const string RuntimeInvisibleAnnotations = "RuntimeInvisibleAnnotations";
public const string RuntimeInvisibleParameterAnnotations = "RuntimeInvisibleParameterAnnotations";

ushort nameIndex;

Expand Down Expand Up @@ -115,6 +116,7 @@ static AttributeInfo CreateAttribute (string name, ConstantPool constantPool, us
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 RuntimeInvisibleParameterAnnotations: return new RuntimeInvisibleParameterAnnotationsAttribute (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 @@ -543,7 +545,32 @@ public RuntimeInvisibleAnnotationsAttribute (ConstantPool constantPool, ushort n
public override string ToString ()
{
var annotations = string.Join (", ", Annotations.Select (v => v.ToString ()));
return $"RuntimeVisibleAnnotations({annotations})";
return $"RuntimeInvisibleAnnotations({annotations})";
}
}


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

public RuntimeInvisibleParameterAnnotationsAttribute (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 $"RuntimeInvisibleParameterAnnotationsAttribute({annotations})";
}
}

Expand Down
56 changes: 55 additions & 1 deletion src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ XElement GetMethod (string element, string name, MethodInfo method, string retur
new XAttribute ("bridge", (method.AccessFlags & MethodAccessFlags.Bridge) != 0),
new XAttribute ("synthetic", (method.AccessFlags & MethodAccessFlags.Synthetic) != 0),
new XAttribute ("jni-signature", method.Descriptor),
GetNotNull (method),
GetTypeParmeters (msig == null ? null : msig.TypeParameters),
GetMethodParameters (method),
GetExceptions (method));
Expand Down Expand Up @@ -382,6 +383,7 @@ static string GetVisibility (MethodAccessFlags accessFlags)

IEnumerable<XElement> GetMethodParameters (MethodInfo method)
{
var annotations = method.Attributes?.OfType<RuntimeInvisibleParameterAnnotationsAttribute> ().FirstOrDefault ()?.Annotations;
var varargs = (method.AccessFlags & MethodAccessFlags.Varargs) != 0;
var parameters = method.GetParameters ();
for (int i = 0; i < parameters.Length; ++i) {
Expand All @@ -405,7 +407,8 @@ IEnumerable<XElement> GetMethodParameters (MethodInfo method)
yield return new XElement ("parameter",
new XAttribute ("name", p.Name),
new XAttribute ("type", genericType),
new XAttribute ("jni-type", p.Type.TypeSignature));
new XAttribute ("jni-type", p.Type.TypeSignature),
GetNotNull (annotations, i));
}
}

Expand All @@ -421,6 +424,56 @@ IEnumerable<XElement> GetExceptions (MethodInfo method)
}
}

static XAttribute GetNotNull (MethodInfo method)
{
var annotations = method.Attributes?.OfType<RuntimeInvisibleAnnotationsAttribute> ().FirstOrDefault ()?.Annotations;

if (annotations?.Any (a => IsNotNullAnnotation (a)) == true)
return new XAttribute ("return-not-null", "true");

return null;
}

static XAttribute GetNotNull (IList<ParameterAnnotation> annotations, int parameterIndex)
{
var ann = annotations?.FirstOrDefault (a => a.ParameterIndex == parameterIndex)?.Annotations;

if (ann?.Any (a => IsNotNullAnnotation (a)) == true)
return new XAttribute ("not-null", "true");

return null;
}

static XAttribute GetNotNull (FieldInfo field)
{
var annotations = field.Attributes?.OfType<RuntimeInvisibleAnnotationsAttribute> ().FirstOrDefault ()?.Annotations;

if (annotations?.Any (a => IsNotNullAnnotation (a)) == true)
return new XAttribute ("not-null", "true");

return null;
}

static bool IsNotNullAnnotation (Annotation annotation)
{
// Android ones plus the list from here:
// https://stackoverflow.com/questions/4963300/which-notnull-java-annotation-should-i-use
switch (annotation.Type) {
case "Landroid/annotation/NonNull;":
case "Landroidx/annotation/RecentlyNonNull;":
case "Ljavax/validation/constraints/NotNull;":
case "Ledu/umd/cs/findbugs/annotations/NonNull;":
case "Ljavax/annotation/Nonnull;":
case "Lorg/jetbrains/annotations/NotNull;":
case "Llombok/NonNull;":
case "Landroid/support/annotation/NonNull;":
case "Lorg/eclipse/jdt/annotation/NonNull;":
return true;
}

return false;
}

IEnumerable<XElement> GetFields ()
{
foreach (var field in classFile.Fields.OrderBy (n => n.Name, StringComparer.OrdinalIgnoreCase)) {
Expand All @@ -437,6 +490,7 @@ IEnumerable<XElement> GetFields ()
new XAttribute ("type", SignatureToJavaTypeName (field.Descriptor)),
new XAttribute ("type-generic-aware", GetGenericType (field)),
new XAttribute ("jni-signature", field.Descriptor),
GetNotNull (field),
GetValue (field),
new XAttribute ("visibility", visibility),
new XAttribute ("volatile", (field.AccessFlags & FieldAccessFlags.Volatile) != 0));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;

using Xamarin.Android.Tools.Bytecode;

using NUnit.Framework;
using System.Linq;

namespace Xamarin.Android.Tools.BytecodeTests
{
[TestFixture]
public class NullableAnnotationTests : ClassFileFixture
{
[Test]
public void RuntimeInvisibleAnnotations ()
{
var c = LoadClassFile ("NotNullClass.class");

// Method with no annotations
var null_method = c.Methods.First (m => m.Name == "nullFunc");

Assert.AreEqual (0, null_method.Attributes.OfType<RuntimeInvisibleAnnotationsAttribute> ().Count ());
Assert.AreEqual (0, null_method.Attributes.OfType<RuntimeInvisibleParameterAnnotationsAttribute> ().Count ());

// Method with not-null parameter and return value annotations
var notnull_method = c.Methods.First (m => m.Name == "notNullFunc");
var return_ann = notnull_method.Attributes.OfType<RuntimeInvisibleAnnotationsAttribute> ().FirstOrDefault ()?.Annotations;
var param_ann = notnull_method.Attributes.OfType<RuntimeInvisibleParameterAnnotationsAttribute> ().FirstOrDefault ()?.Annotations;

Assert.NotNull (return_ann);
Assert.IsTrue (return_ann.Any (a => a.Type == "Landroid/annotation/NonNull;"));

Assert.NotNull (param_ann);
Assert.IsTrue (param_ann.Any (a => a.ParameterIndex == 0 && a.Annotations[0].Type == "Landroid/annotation/NonNull;"));

// Field with no annotations
var null_field = c.Fields.First (f => f.Name == "nullField");

Assert.AreEqual (0, null_field.Attributes.OfType<RuntimeInvisibleAnnotationsAttribute> ().Count ());
Assert.AreEqual (0, null_field.Attributes.OfType<RuntimeInvisibleParameterAnnotationsAttribute> ().Count ());

// Field with not-null annotation
var notnull_field = c.Fields.First (f => f.Name == "notNullField");

var field_ann = notnull_method.Attributes.OfType<RuntimeInvisibleAnnotationsAttribute> ().FirstOrDefault ()?.Annotations;

Assert.NotNull (field_ann);
Assert.IsTrue (field_ann.Any (a => a.Type == "Landroid/annotation/NonNull;"));
}

[Test]
public void NullableAnnotationOutput ()
{
var c = LoadClassFile ("NotNullClass.class");
var builder = new XmlClassDeclarationBuilder (c);
var xml = builder.ToXElement ();

var method = xml.Elements ("method").First (m => m.Attribute ("name").Value == "notNullFunc");
Assert.AreEqual ("true", method.Attribute ("return-not-null").Value);

var parameter = method.Element ("parameter");
Assert.AreEqual ("true", parameter.Attribute ("not-null").Value);

var field = xml.Elements ("field").First (f => f.Attribute ("name").Value == "notNullField");
Assert.AreEqual ("true", field.Attribute ("not-null").Value);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<EmbeddedResource Include="Resources\*" />
<EmbeddedResource Include="kotlin\**\*.class" />

<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\NotNullClass.class" />
<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\IJavaInterface.class" />
<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\IParameterInterface.class" />
<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\JavaAnnotation.class" />
Expand All @@ -51,14 +52,14 @@
</ItemGroup>

<ItemGroup>
<TestJar Include="java\**\*.java" Exclude="java\java\util\Collection.java" />
<TestJar Include="java\**\*.java" Exclude="java\java\util\Collection.java,java\android\annotation\NonNull.java" />
<TestJarNoParameters Include="java\java\util\Collection.java" />
<TestKotlinJar Include="kotlin\**\*.kt" />
</ItemGroup>

<Target Name="BuildClasses" BeforeTargets="BeforeBuild" Inputs="@(TestJar)" Outputs="@(TestJar->'$(IntermediateOutputPath)classes\%(RecursiveDir)%(Filename).class')">
<MakeDir Directories="$(IntermediateOutputPath)classes" />
<Exec Command="&quot;$(JavaCPath)&quot; -parameters $(_JavacSourceOptions) -g -d &quot;$(IntermediateOutputPath)classes&quot; @(TestJar->'%(Identity)', ' ')" />
<Exec Command="&quot;$(JavaCPath)&quot; -parameters $(_JavacSourceOptions) -g -d &quot;$(IntermediateOutputPath)classes&quot; java\android\annotation\NonNull.java @(TestJar->'%(Identity)', ' ')" />
<Exec Command="&quot;$(JavaCPath)&quot; $(_JavacSourceOptions) -g -d &quot;$(IntermediateOutputPath)classes&quot; @(TestJarNoParameters->'%(Identity)', ' ')" />
</Target>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package android.annotation;

import java.lang.annotation.*;
import java.lang.reflect.*;

public @interface NonNull {
}
Loading