Skip to content

Commit

Permalink
[generator] Add support for @RestrictTo.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpobst committed Apr 10, 2023
1 parent 0355acf commit be11f7c
Show file tree
Hide file tree
Showing 30 changed files with 239 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ static void SaveTypeParameters (JavaTypeParameters parameters, XmlWriter writer)
}

static void SaveConstructor (JavaConstructorModel ctor, XmlWriter writer)
=> SaveMember (ctor, writer, "constructor", null, null, null, null, null, ctor.DeclaringType.FullName, null, null, null, ctor.Parameters, ctor.IsBridge, null, ctor.IsSynthetic, null);
=> SaveMember (ctor, writer, "constructor", null, null, null, null, null, ctor.DeclaringType.FullName, null, null, null, ctor.Parameters, ctor.IsBridge, null, ctor.IsSynthetic, null, ctor.AnnotatedVisibility);

static void SaveField (JavaFieldModel field, XmlWriter writer)
{
Expand All @@ -177,7 +177,8 @@ static void SaveField (JavaFieldModel field, XmlWriter writer)
null,
null,
null,
field.IsNotNull);
field.IsNotNull,
field.AnnotatedVisibility);
}

static void SaveMethod (JavaMethodModel method, XmlWriter writer)
Expand Down Expand Up @@ -228,7 +229,8 @@ bool check (JavaMethodModel _) => _.BaseMethod?.DeclaringType?.Visibility == "pu
extBridge: method.IsBridge,
jniReturn: method.ReturnJni,
extSynthetic: method.IsSynthetic,
notNull: method.ReturnNotNull);
notNull: method.ReturnNotNull,
annotatedVisibility: method.AnnotatedVisibility);
}

static string GetVisibleReturnTypeString (JavaMethodModel method)
Expand Down Expand Up @@ -277,7 +279,7 @@ static void SaveMember (JavaMemberModel m, XmlWriter writer, string elementName,
string? transient, string? type, string? typeGeneric,
string? value, string? volat,
IEnumerable<JavaParameterModel>? parameters,
bool? extBridge, string? jniReturn, bool? extSynthetic, bool? notNull)
bool? extBridge, string? jniReturn, bool? extSynthetic, bool? notNull, string? annotatedVisibility)
{
// If any of the parameters contain reference to non-public type, it cannot be generated.
// TODO
Expand Down Expand Up @@ -310,6 +312,7 @@ static void SaveMember (JavaMemberModel m, XmlWriter writer, string elementName,
writer.WriteAttributeStringIfValue ("type", type);
writer.WriteAttributeStringIfValue ("type-generic-aware", typeGeneric);
writer.WriteAttributeStringIfValue ("value", value);
writer.WriteAttributeStringIfValue ("annotated-visibility", annotatedVisibility);

if (extSynthetic.HasValue)
writer.WriteAttributeString ("synthetic", extSynthetic.Value ? "true" : "false");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ public static JavaMethodModel ParseMethod (JavaTypeModel type, XElement element)
returnJni: element.XGetAttribute ("jni-return"),
isNative: element.XGetAttributeAsBool ("native"),
isSynchronized: element.XGetAttributeAsBool ("synchronized"),
returnNotNull: element.XGetAttributeAsBool ("return-not-null")
returnNotNull: element.XGetAttributeAsBool ("return-not-null"),
annotatedVisibility: element.XGetAttributeOrNull ("annotated-visibility")
);

if (element.Element ("typeParameters") is XElement tp)
Expand Down Expand Up @@ -226,7 +227,8 @@ public static JavaConstructorModel ParseConstructor (JavaTypeModel type, XElemen
deprecated: element.XGetAttribute ("deprecated"),
jniSignature: element.XGetAttribute ("jni-signature"),
isSynthetic: element.XGetAttributeAsBool ("synthetic"),
isBridge: element.XGetAttributeAsBool ("bridge")
isBridge: element.XGetAttributeAsBool ("bridge"),
annotatedVisibility: element.XGetAttributeOrNull ("annotated-visibility")
);

// Yes, constructors in Java can have generic type parameters ¯\_(ツ)_/¯
Expand Down Expand Up @@ -262,7 +264,8 @@ public static JavaFieldModel ParseField (JavaTypeModel type, XElement element)
jniSignature: element.XGetAttribute ("jni-signature"),
isTransient: element.XGetAttributeAsBool ("transient"),
isVolatile: element.XGetAttributeAsBool ("volatile"),
isNotNull: element.XGetAttributeAsBool ("not-null")
isNotNull: element.XGetAttributeAsBool ("not-null"),
annotatedVisibility: element.XGetAttributeOrNull ("annotated-visibility")
);

if (element.XGetAttribute ("merge.SourceFile") is string source && source.HasValue ())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ static bool ShouldImport (TypeDefinition td)
returnJni: jni_signature.Return.Jni,
isNative: false,
isSynchronized: false,
returnNotNull: false
returnNotNull: false,
annotatedVisibility: null
);

for (var i = 0; i < jni_signature.Parameters.Count; i++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ namespace Java.Interop.Tools.JavaTypeSystem.Models
{
public class JavaConstructorModel : JavaMethodModel
{
public JavaConstructorModel (string javaName, string javaVisibility, bool javaStatic, JavaTypeModel javaDeclaringType, string deprecated, string jniSignature, bool isSynthetic, bool isBridge)
: base (javaName, javaVisibility, false, false, javaStatic, "void", javaDeclaringType, deprecated, jniSignature, isSynthetic, isBridge, string.Empty, false, false, false)
public JavaConstructorModel (string javaName, string javaVisibility, bool javaStatic, JavaTypeModel javaDeclaringType, string deprecated, string jniSignature, bool isSynthetic, bool isBridge, string? annotatedVisibility)
: base (javaName, javaVisibility, false, false, javaStatic, "void", javaDeclaringType, deprecated, jniSignature, isSynthetic, isBridge, string.Empty, false, false, false, annotatedVisibility)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public class JavaFieldModel : JavaMemberModel

public JavaTypeReference? TypeModel { get; private set; }

public JavaFieldModel (string name, string visibility, string type, string typeGeneric, string? value, bool isStatic, JavaTypeModel declaringType, bool isFinal, string deprecated, string jniSignature, bool isTransient, bool isVolatile, bool isNotNull)
: base (name, isStatic, isFinal, visibility, declaringType, deprecated, jniSignature)
public JavaFieldModel (string name, string visibility, string type, string typeGeneric, string? value, bool isStatic, JavaTypeModel declaringType, bool isFinal, string deprecated, string jniSignature, bool isTransient, bool isVolatile, bool isNotNull, string? annotatedVisibility)
: base (name, isStatic, isFinal, visibility, declaringType, deprecated, jniSignature, annotatedVisibility)
{
Type = type;
TypeGeneric = typeGeneric;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ public abstract class JavaMemberModel : IJavaResolvable
public string Visibility { get; }
public string Deprecated { get; }
public string JniSignature { get; }
public string? AnnotatedVisibility { get; }

public Dictionary<string, string> PropertyBag { get; } = new Dictionary<string, string> ();

public JavaMemberModel (string name, bool isStatic, bool isFinal, string visibility, JavaTypeModel declaringType, string deprecated, string jniSignature)
public JavaMemberModel (string name, bool isStatic, bool isFinal, string visibility, JavaTypeModel declaringType, string deprecated, string jniSignature, string? annotatedVisibility)
{
Name = name;
IsStatic = isStatic;
Expand All @@ -24,6 +25,7 @@ public JavaMemberModel (string name, bool isStatic, bool isFinal, string visibil
DeclaringType = declaringType;
Deprecated = deprecated;
JniSignature = jniSignature;
AnnotatedVisibility = annotatedVisibility;
}

public abstract void Resolve (JavaTypeCollection types, ICollection<JavaUnresolvableModel> unresolvables);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class JavaMethodModel : JavaMemberModel
public List<JavaParameterModel> Parameters { get; } = new List<JavaParameterModel> ();
public List<JavaExceptionModel> Exceptions { get; } = new List<JavaExceptionModel> ();

public JavaMethodModel (string javaName, string javaVisibility, bool javaAbstract, bool javaFinal, bool javaStatic, string javaReturn, JavaTypeModel javaDeclaringType, string deprecated, string jniSignature, bool isSynthetic, bool isBridge, string returnJni, bool isNative, bool isSynchronized, bool returnNotNull)
: base (javaName, javaStatic, javaFinal, javaVisibility, javaDeclaringType, deprecated, jniSignature)
public JavaMethodModel (string javaName, string javaVisibility, bool javaAbstract, bool javaFinal, bool javaStatic, string javaReturn, JavaTypeModel javaDeclaringType, string deprecated, string jniSignature, bool isSynthetic, bool isBridge, string returnJni, bool isNative, bool isSynchronized, bool returnNotNull, string? annotatedVisibility)
: base (javaName, javaStatic, javaFinal, javaVisibility, javaDeclaringType, deprecated, jniSignature, annotatedVisibility)
{
IsAbstract = javaAbstract;
Return = javaReturn;
Expand Down
13 changes: 13 additions & 0 deletions src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public XElement ToXElement ()
GetSourceFile (),
new XAttribute ("static", classFile.IsStatic),
new XAttribute ("visibility", GetVisibility (classFile.Visibility)),
GetAnnotatedVisibility (classFile.Attributes),
GetTypeParmeters (signature == null ? null : signature.TypeParameters),
GetImplementedInterfaces (),
GetConstructors (),
Expand Down Expand Up @@ -346,6 +347,7 @@ XElement GetMethod (string element, string name, MethodInfo method, string? retu
new XAttribute ("static", (method.AccessFlags & MethodAccessFlags.Static) != 0),
GetSynchronized (method),
new XAttribute ("visibility", GetVisibility (method.AccessFlags)),
GetAnnotatedVisibility (method.Attributes),
new XAttribute ("bridge", (method.AccessFlags & MethodAccessFlags.Bridge) != 0),
new XAttribute ("synthetic", (method.AccessFlags & MethodAccessFlags.Synthetic) != 0),
new XAttribute ("jni-signature", method.Descriptor),
Expand Down Expand Up @@ -427,6 +429,16 @@ IEnumerable<XElement> GetExceptions (MethodInfo method)
}
}

static XAttribute? GetAnnotatedVisibility (AttributeCollection attributes)
{
var annotations = attributes?.OfType<RuntimeInvisibleAnnotationsAttribute> ().FirstOrDefault ()?.Annotations;

if (annotations?.FirstOrDefault (a => a.Type == "Landroidx/annotation/RestrictTo;") is Annotation annotation)
return new XAttribute ("annotated-visibility", ((annotation.Values.FirstOrDefault ().Value as AnnotationElementArray)?.Values?.FirstOrDefault () as AnnotationElementEnum)?.ConstantName ?? string.Empty);

return null;
}

static XAttribute? GetNotNull (MethodInfo method)
{
var annotations = method.Attributes?.OfType<RuntimeInvisibleAnnotationsAttribute> ().FirstOrDefault ()?.Annotations;
Expand Down Expand Up @@ -500,6 +512,7 @@ IEnumerable<XElement> GetFields ()
GetNotNull (field),
GetValue (field),
new XAttribute ("visibility", visibility),
GetAnnotatedVisibility (field.Attributes),
new XAttribute ("volatile", (field.AccessFlags & FieldAccessFlags.Volatile) != 0));
}
}
Expand Down
138 changes: 138 additions & 0 deletions tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,144 @@ public void ObsoleteEntireProperty ()
Assert.False (StripRegisterAttributes (writer.ToString ()).NormalizeLineEndings ().Contains ("[global::System.Obsolete(@\"deprecated\")]set"), writer.ToString ());
}

[Test]
public void RestrictToType ()
{
options.UseRestrictToAttributes = true;

var xml = @"<api>
<package name='java.lang' jni-name='java/lang'>
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
</package>
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;' annotated-visibility='LIBRARY_GROUP_PREFIX' />
</package>
</api>";

var gens = ParseApiDefinition (xml);
var iface = gens.Single (g => g.Name == "MyClass");

generator.Context.ContextTypes.Push (iface);
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

// This should use a special [Obsolete] describing the "internal" nature of this API
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Obsolete (\"While this type is 'public', Google considers it internal API and reserves the right to modify or delete it in the future. Use at your own risk.\", DiagnosticId = \"XAOBS001\")]".NormalizeLineEndings ()), writer.ToString ());
}

[Test]
public void RestrictToField ()
{
options.UseRestrictToAttributes = true;

var xml = @"<api>
<package name='java.lang' jni-name='java/lang'>
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
</package>
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;'>
<field deprecated='not deprecated' name='ACCEPT_HANDOVER' jni-signature='Ljava/lang/String;' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' visibility='public' volatile='false' annotated-visibility='LIBRARY_GROUP_PREFIX'></field>
</class>
</package>
</api>";

var gens = ParseApiDefinition (xml);
var iface = gens.Single (g => g.Name == "MyClass");

generator.Context.ContextTypes.Push (iface);
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

// This should use a special [Obsolete] describing the "internal" nature of this API
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Obsolete (\"While this member is 'public', Google considers it internal API and reserves the right to modify or delete it in the future. Use at your own risk.\", DiagnosticId = \"XAOBS001\")]".NormalizeLineEndings ()), writer.ToString ());
}

[Test]
public void RestrictToMethod ()
{
options.UseRestrictToAttributes = true;

var xml = @"<api>
<package name='java.lang' jni-name='java/lang'>
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
</package>
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;'>
<method abstract='false' final='false' name='countAffectedRows' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' annotated-visibility='LIBRARY_GROUP_PREFIX'></method>
</class>
</package>
</api>";

var gens = ParseApiDefinition (xml);
var iface = gens.Single (g => g.Name == "MyClass");

generator.Context.ContextTypes.Push (iface);
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

// This should use a special [Obsolete] describing the "internal" nature of this API
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Obsolete (\"While this member is 'public', Google considers it internal API and reserves the right to modify or delete it in the future. Use at your own risk.\", DiagnosticId = \"XAOBS001\")]".NormalizeLineEndings ()), writer.ToString ());
}

[Test]
public void RestrictToProperty ()
{
options.UseRestrictToAttributes = true;

var xml = @"<api>
<package name='java.lang' jni-name='java/lang'>
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
</package>
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;'>
<method abstract='false' deprecated='not deprecated' final='false' name='getCount' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' annotated-visibility='LIBRARY_GROUP_PREFIX'></method>
<method abstract='false' deprecated='not deprecated' final='false' name='setCount' jni-signature='(I)V' bridge='false' native='false' return='void' jni-return='V' static='false' synchronized='false' synthetic='false' visibility='public' annotated-visibility='LIBRARY_GROUP_PREFIX'>
<parameter name='count' type='int' jni-type='I'></parameter>
</method>
</class>
</package>
</api>";

var gens = ParseApiDefinition (xml);
var iface = gens.Single (g => g.Name == "MyClass");

generator.Context.ContextTypes.Push (iface);
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

// This should use a special [Obsolete] describing the "internal" nature of this API
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Obsolete (\"While this member is 'public', Google considers it internal API and reserves the right to modify or delete it in the future. Use at your own risk.\", DiagnosticId = \"XAOBS001\")]".NormalizeLineEndings ()), writer.ToString ());
}

[Test]
public void DoNotWriteObsoleteAndRestrictTo ()
{
options.UseRestrictToAttributes = true;

var xml = @"<api>
<package name='java.lang' jni-name='java/lang'>
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
</package>
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;'>
<method deprecated='deprecated' abstract='false' final='false' name='countAffectedRows' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' annotated-visibility='LIBRARY_GROUP_PREFIX'></method>
</class>
</package>
</api>";

var gens = ParseApiDefinition (xml);
var iface = gens.Single (g => g.Name == "MyClass");

generator.Context.ContextTypes.Push (iface);
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

// This method is both @Deprecated and @RestrictTo. We cannot write 2 [Obsolete] attributes, so
// only write the deprecated one.
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Obsolete]".NormalizeLineEndings ()), writer.ToString ());
Assert.False (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Obsolete (\"While this member is 'public', Google considers it internal API and reserves the right to modify or delete it in the future. Use at your own risk.\", DiagnosticId = \"XAOBS001\")]".NormalizeLineEndings ()), writer.ToString ());
}

[Test]
[NonParallelizable] // We are setting a static property on Report
public void WarnIfTypeNameMatchesNamespace ()
Expand Down
1 change: 1 addition & 0 deletions tools/generator/CodeGenerationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public SymbolTable SymbolTable {
public bool SupportNullableReferenceTypes { get; set; }
public bool UseShallowReferencedTypes { get; set; }
public bool UseObsoletedOSPlatformAttributes { get; set; }
public bool UseRestrictToAttributes { get; set; }
public bool RemoveConstSugar => BuildingCoreAssembly;

bool? buildingCoreAssembly;
Expand Down
Loading

0 comments on commit be11f7c

Please sign in to comment.