Skip to content

Commit dbb9edd

Browse files
authored
[Java.Interop.Tools.JavaTypeSystem] Prefer !synthetic base methods (#1110)
Context: dotnet/android-libraries#723 Imagine we have [the following Java][0]: public abstract class Key { public abstract Parameters getParameters(); } public abstract class StreamingAeadKey extends Key { @OverRide public abstract StreamingAeadParameters getParameters(); } public final class AesGcmHkdfStreamingKey extends StreamingAeadKey { @OverRide public AesGcmHkdfStreamingParameters getParameters() { ... } } The reabstraction in `StreamingAeadKey.getParameters()` causes an additional synthetic `getParameters()` method to be generated: <class name="StreamingAeadKey" abstract="true" deprecated="not deprecated" jni-extends="Lcom/google/crypto/tink/Key;" extends="com.google.crypto.tink.Key" extends-generic-aware="com.google.crypto.tink.Key" final="false" jni-signature="Lcom/google/crypto/tink/streamingaead/StreamingAeadKey;" source-file-name="StreamingAeadKey.java" static="false" visibility="public"> <method abstract="false" name="getParameters" deprecated="not deprecated" jni-signature="()Lcom/google/crypto/tink/Parameters;" final="false" native="false" bridge="true" synthetic="true" return="com.google.crypto.tink.Parameters" jni-return="Lcom/google/crypto/tink/Parameters;" static="false" synchronized="false" visibility="public" /> <method abstract="true" name="getParameters" deprecated="not deprecated" jni-signature="()Lcom/google/crypto/tink/streamingaead/StreamingAeadParameters;" final="false" native="false" bridge="false" synthetic="false" return="com.google.crypto.tink.streamingaead.StreamingAeadParameters" jni-return="Lcom/google/crypto/tink/streamingaead/StreamingAeadParameters;" static="false" synchronized="false" visibility="public" /> </class> Rephrasing in Java, we have: /* partial */ class StreamingAeadKey extends Key { // abstract=false, bridge=true, synthetic=true public Parameters getParameters() {…} // abstract=true, bridge=false, synthetic=false public abstract StreamingAeadParameters getParameters(); } When looking for the "base method" for `AesGcmHkdfStreamingKey.getParameters()` we grab the "first" one we find with a matching name and parameters. In this case, it is the synthetic method instead of the "regular" method. Note that the synthetic method is not `abstract`. When deciding if we need to output `AesGcmHkdfStreamingKey.getParameters()`, we see that both it and the "base method" are not `abstract`, so we don't need to bother outputting the derived method. In reality, the base method should be the `abstract` method. Then it is different from the derived non-abstract method, and we will output the derived method to `api.xml.adjusted`. The fix is to prefer non-`synthetic`, non-`bridge` methods when choosing base methods. [0]: https://github.com/google/tink/blob/9bc2667963e20eb42611b7581e570f0dddf65a2b/java_src/src/main/java/com/google/crypto/tink/Key.java
1 parent 3038d60 commit dbb9edd

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

src/Java.Interop.Tools.JavaTypeSystem/JavaModels/JavaMethodModel.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,15 @@ public void FindBaseMethod (JavaClassModel? type)
7777

7878
var pt = (JavaClassModel)DeclaringType;
7979

80-
var candidate = type.Methods.FirstOrDefault (p => p.Name == Name && IsImplementing (this, p, pt.GenericInheritanceMapping ?? throw new InvalidOperationException ($"missing {nameof (pt.GenericInheritanceMapping)}!")));
80+
var candidates = type.Methods.Where (p => p.Name == Name && IsImplementing (this, p, pt.GenericInheritanceMapping ?? throw new InvalidOperationException ($"missing {nameof (pt.GenericInheritanceMapping)}!")));
81+
82+
JavaMethodModel? candidate;
83+
84+
// Prefer non-synthetic, non-bridge methods
85+
if (candidates.FirstOrDefault (c => !c.IsSynthetic && !c.IsBridge) is JavaMethodModel jm)
86+
candidate = jm;
87+
else
88+
candidate = candidates.FirstOrDefault ();
8189

8290
if (candidate != null) {
8391
BaseMethod = candidate;

tests/Java.Interop.Tools.JavaTypeSystem-Tests/BaseMethodTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Linq;
3+
using System.Text;
4+
using System.Xml;
35
using Java.Interop.Tools.JavaTypeSystem.Models;
46
using NUnit.Framework;
57

@@ -83,5 +85,57 @@ public void GenericConstructors ()
8385
var m = t.Constructors.FirstOrDefault ();
8486
Assert.IsNotNull (m.TypeParameters, "constructor not found");
8587
}
88+
89+
[Test]
90+
public void PreferRealApi ()
91+
{
92+
// Our base method is abstract, and our derived method is not. However their is also a "matching" non-abstract
93+
// base method that is "synthetic, bridge". If this method is chosen as the base then we will not write the derived
94+
// method because it is not "different" from the base. (They are both not-abstract.) In reality, we need to
95+
// match to the "not-synthetic, not-bridge" method that *is* abstract, so that the derived method is different
96+
// enough to get written to the output.
97+
// See "JavaXmlApiExporter.SaveMethod ()" for what constitutes "different" enough to be written.
98+
string xml = @"<api>
99+
<package name='com.google.crypto.tink.streamingaead' jni-name='com/google/crypto/tink/streamingaead'>
100+
101+
<class abstract='true' deprecated='not deprecated' jni-extends='Ljava/lang/Object;' extends='java.lang.Object' extends-generic-aware='java.lang.Object' final='false' name='StreamingAeadKey' jni-signature='Lcom/google/crypto/tink/streamingaead/StreamingAeadKey;' source-file-name='StreamingAeadKey.java' static='false' visibility='public'>
102+
<method abstract='false' deprecated='not deprecated' final='false' name='getParameters' native='false' return='java.lang.Object' jni-return='Ljava/lang/Object;' static='false' synchronized='false' visibility='public' bridge='true' synthetic='true' jni-signature='()Ljava/lang/Object;' />
103+
<method abstract='true' deprecated='not deprecated' final='false' name='getParameters' native='false' return='java.lang.Object' jni-return='Ljava/lang/Object;' static='false' synchronized='false' visibility='public' bridge='false' synthetic='false' jni-signature='()Ljava/lang/Object;' />
104+
</class>
105+
106+
<class abstract='false' deprecated='not deprecated' jni-extends='Lcom/google/crypto/tink/streamingaead/StreamingAeadKey;' extends='com.google.crypto.tink.streamingaead.StreamingAeadKey' extends-generic-aware='com.google.crypto.tink.streamingaead.StreamingAeadKey' final='true' name='AesGcmHkdfStreamingKey' jni-signature='Lcom/google/crypto/tink/streamingaead/AesGcmHkdfStreamingKey;' source-file-name='AesGcmHkdfStreamingKey.java' static='false' visibility='public'>
107+
<method abstract='false' deprecated='not deprecated' final='false' name='getParameters' native='false' return='java.lang.Object' jni-return='Ljava/lang/Object;' static='false' synchronized='false' visibility='public' bridge='false' synthetic='false' jni-signature='()Ljava/lang/Object;' />
108+
</class>
109+
110+
</package>
111+
</api>";
112+
113+
var xapi = JavaApiTestHelper.GetLoadedApi ();
114+
JavaXmlApiImporter.ParseString (xml, xapi);
115+
116+
var results = xapi.ResolveCollection ();
117+
118+
var t = xapi.Packages ["com.google.crypto.tink.streamingaead"].Types.First (_ => _.Name == "AesGcmHkdfStreamingKey") as JavaClassModel;
119+
var m = t.Methods.FirstOrDefault ();
120+
121+
// The non-synthetic, non-bridge, abstract base method should be chosen
122+
Assert.IsFalse (m.BaseMethod.IsSynthetic);
123+
Assert.IsFalse (m.BaseMethod.IsBridge);
124+
Assert.IsTrue (m.BaseMethod.IsAbstract);
125+
126+
var sb = new StringBuilder ();
127+
128+
// Write the results out to XML
129+
using (var xw = XmlWriter.Create (sb))
130+
JavaXmlApiExporter.Save (xapi, xw);
131+
132+
// Read results back in to ensure AesGcmHkdfStreamingKey.getParameters was output
133+
var new_collection = JavaXmlApiImporter.ParseString (sb.ToString ());
134+
135+
var t2 = new_collection.Packages ["com.google.crypto.tink.streamingaead"].Types.First (_ => _.Name == "AesGcmHkdfStreamingKey") as JavaClassModel;
136+
var m2 = t.Methods.FirstOrDefault ();
137+
138+
Assert.IsNotNull (m2);
139+
}
86140
}
87141
}

0 commit comments

Comments
 (0)