Skip to content

Commit ab3c2b2

Browse files
jonpryoratsushieno
authored andcommitted
[Java.Interop.Tools.JavaCallableWrappers] Error on bad IJavaObject (#179)
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=56819 Nothing *prevents* "anybody" from custom implementing `IJavaObject`: class MyBadClass : Android.Runtime.IJavaObject { public IntPtr Handle { get {return IntPtr.Zero;} } public void Dispose () {} } The problem is that the above doesn't actually work: the `IJavaObject.Handle` value contains the JNI Object Reference to pass into JNI. The above code will thus result in always passing `null` into Java code, which could result in a `NullPointerException`, but will always result in *not* doing what was intended. While it is *theoretically* possible to manually implement `IJavaObject`, it's not something *I* would want to contemplate, nor is it for the faint of heart, so long ago we decided to emit a warning if we encounter a type which: 1. Implements `Android.Runtime.IJavaObject`, and 2. Does *not* also inherit `Java.Lang.Object` or `Java.Lang.Throwable`. As such, the above `MyBadClass` would elicit the warning: Type 'MyBadClass' implements Android.Runtime.IJavaObject but does not inherit from Java.Lang.Object. It is not supported. This is all well and good, but (effectively) *nobody* reads warnings, *especially* since our toolchain emits so many warnings that enabling `/warnaserror` is for the truly foolhardy, so *lots* of warnings is, unfortunately, normal. Which brings us to Bug #56819: Could we make this an error? Answer: Of course we can. That said, I have no idea how much existing code this will *break* if we turned it into an error. That might be good and useful, but if there's no way to *disable* the error, existing apps which (appear to) work will no longer build. That's not desirable. Turn `JavaTypeScanner` from a `static` class into a normal class, and add a new `JavaTypeScanner.ErrorOnCustomJavaObject` property which, when true, will cause `JavaTypeScanner` to emit an error instead of a warning when it encounters types such as `MyBadClass`. (I'm turning it into a normal class instead of adding a new `JavaTypeScanner.GetJavaTypes()` overload in case we need to add additional such "control" parameters in the future. Method overloads will otherwise get unwieldy.) `JavaTypeScanner.ErrorOnCustomJavaObject` is all well and good, but how do we *actually* emit a warning vs. an error? The previous `Action<string, object[]> log` delegate doesn't allow distinguishing between warnings and errors (and possible diagnostic messages), so how would we eventually hook this up into xamarin-android's `<GenerateJavaStubs/>` task? Attempt to better handle that question by replacing `Action<string, object[]>` delegates with `Action<TraceLevel, string>` delegates. This allows messages to explicitly specify TraceLevel.Error, TraceLevel.Warning, or TraceLevel.Info (or others!) so that messages can be sanely processed by downstream users. Additionally, add a new `Diagnostic.CreateConsoleLogger()` method which creates an `Action<TraceLevel, string>` which forwards messages to `Console.Error` or `Console.Out`, as appropriate.
1 parent bac28ab commit ab3c2b2

File tree

7 files changed

+111
-42
lines changed

7 files changed

+111
-42
lines changed

src/Java.Interop.Tools.Cecil/Java.Interop.Tools.Cecil/DirectoryAssemblyResolver.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
//
2929

3030
using System;
31+
using System.Diagnostics;
3132
using System.Collections;
3233
using System.Collections.Generic;
3334
using System.IO;
@@ -62,20 +63,28 @@ public class DirectoryAssemblyResolver : IAssemblyResolver {
6263

6364
Dictionary<string, AssemblyDefinition> cache;
6465
bool loadDebugSymbols;
65-
Action<string, object[]> logWarnings;
66+
Action<TraceLevel, string> logger;
6667

6768
ReaderParameters loadReaderParameters;
6869

6970
static readonly ReaderParameters DefaultLoadReaderParameters = new ReaderParameters {
7071
};
7172

73+
[Obsolete ("Use DirectoryAssemblyResolver(Action<TraceLevel, string>, bool, ReaderParameters)")]
7274
public DirectoryAssemblyResolver (Action<string, object[]> logWarnings, bool loadDebugSymbols, ReaderParameters loadReaderParameters = null)
75+
: this ((TraceLevel level, string value) => logWarnings?.Invoke ("{0}", new[]{value}), loadDebugSymbols, loadReaderParameters)
7376
{
7477
if (logWarnings == null)
7578
throw new ArgumentNullException (nameof (logWarnings));
79+
}
80+
81+
public DirectoryAssemblyResolver (Action<TraceLevel, string> logger, bool loadDebugSymbols, ReaderParameters loadReaderParameters = null)
82+
{
83+
if (logger == null)
84+
throw new ArgumentNullException (nameof (logger));
7685
cache = new Dictionary<string, AssemblyDefinition> ();
7786
this.loadDebugSymbols = loadDebugSymbols;
78-
this.logWarnings = logWarnings;
87+
this.logger = logger;
7988
SearchDirectories = new List<string> ();
8089
this.loadReaderParameters = loadReaderParameters ?? DefaultLoadReaderParameters;
8190
}
@@ -141,10 +150,10 @@ protected virtual AssemblyDefinition ReadAssembly (string file)
141150
try {
142151
return AssemblyDefinition.ReadAssembly (file, reader_parameters);
143152
} catch (Exception ex) {
144-
logWarnings (
145-
"Failed to read '{0}' with debugging symbols. Retrying to load it without it. Error details are logged below.",
146-
new [] { file });
147-
logWarnings ("{0}", new [] { ex.ToString () });
153+
logger (
154+
TraceLevel.Warning,
155+
$"Failed to read '{file}' with debugging symbols. Retrying to load it without it. Error details are logged below.");
156+
logger (TraceLevel.Warning, $"{ex.ToString ()}");
148157
reader_parameters.ReadSymbols = false;
149158
return AssemblyDefinition.ReadAssembly (file, reader_parameters);
150159
}

src/Java.Interop.Tools.Diagnostics/Java.Interop.Tools.Diagnostics/Diagnostic.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System;
44
using System.Collections.Generic;
5+
using System.Diagnostics;
56

67
using Mono.Cecil.Cil;
78

@@ -27,14 +28,14 @@ namespace Java.Interop.Tools.Diagnostics {
2728
// XA0013 Profiling support requires sgen to be enabled too
2829
// XA0014 iOS {0} does not support building applications targeting ARMv6
2930
// XA0020 Could not launch mandroid daemon.
30-
31+
3132
// XA0100 EmbeddedNativeLibrary '{0}' is invalid in Android Application project. Please use AndroidNativeLibrary instead.
3233
// XA0101 @(Content) build action is not supported
3334
// XA0102 Lint Warning
3435
// XA0103 Lint Error
3536
// XA0104 Invalid Sequence Point mode
3637
// XA0105 The TargetFrameworkVersion for {0} (v{1}) is greater than the TargetFrameworkVersion for your project (v{2}). You need to increase the TargetFrameworkVersion for your project.
37-
38+
3839
// XA1xxx file copy / symlinks (project related)
3940
// XA10xx installer.cs / mtouch.cs
4041
// XA1001 Could not find an application at the specified directory
@@ -101,6 +102,7 @@ namespace Java.Interop.Tools.Diagnostics {
101102
// XA4209 Failed to create JavaTypeInfo for class: {0} due to {1}
102103
// XA4210 "You need to add a reference to Mono.Android.Export.dll when you use ExportAttribute or ExportFieldAttribute."
103104
// XA4211 AndroidManifest.xml //uses-sdk/@android:targetSdkVersion '{0}' is less than $(TargetFrameworkVersion) '{1}'. Using API-{1} for ACW compilation.
105+
// XA4212 Type `{0}` implements `Android.Runtime.IJavaObject` but does not inherit `Java.Lang.Object` or `Java.Lang.Throwable`. This is not supported.
104106
// XA5xxx GCC and toolchain
105107
// XA32xx .apk generation
106108
// XA4300 Unsupported $(AndroidSupportedAbis) value '{0}'; ignoring.
@@ -176,6 +178,17 @@ public static void WriteTo (System.IO.TextWriter destination, Exception message,
176178
destination.WriteLine ("monodroid: error XA0000: Unexpected error - Please file a bug report at http://bugzilla.xamarin.com. Reason: {0}",
177179
verbose ? message.Message : message.ToString ());
178180
}
181+
182+
public static Action<TraceLevel, string> CreateConsoleLogger ()
183+
{
184+
Action<TraceLevel, string> logger = (level, value) => {
185+
if (level == TraceLevel.Error)
186+
Console.Error.WriteLine (value);
187+
else
188+
Console.WriteLine (value);
189+
};
190+
return logger;
191+
}
179192
}
180193
}
181194

src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaTypeScanner.cs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Linq;
45
using System.Text;
56
using Mono.Cecil;
@@ -10,10 +11,19 @@
1011

1112
namespace Java.Interop.Tools.JavaCallableWrappers
1213
{
13-
public static class JavaTypeScanner
14+
public class JavaTypeScanner
1415
{
15-
// Returns all types for which we need to generate Java delegate types.
16-
public static List<TypeDefinition> GetJavaTypes (IEnumerable<string> assemblies, IAssemblyResolver resolver, Action<string, object[]> log)
16+
public Action<TraceLevel, string> Logger { get; private set; }
17+
public bool ErrorOnCustomJavaObject { get; set; }
18+
19+
public JavaTypeScanner (Action<TraceLevel, string> logger)
20+
{
21+
if (logger == null)
22+
throw new ArgumentNullException (nameof (logger));
23+
Logger = logger;
24+
}
25+
26+
public List<TypeDefinition> GetJavaTypes (IEnumerable<string> assemblies, IAssemblyResolver resolver)
1727
{
1828
var javaTypes = new List<TypeDefinition> ();
1929

@@ -22,32 +32,34 @@ public static List<TypeDefinition> GetJavaTypes (IEnumerable<string> assemblies,
2232

2333
foreach (ModuleDefinition md in assm.Modules) {
2434
foreach (TypeDefinition td in md.Types) {
25-
AddJavaTypes (javaTypes, td, log);
35+
AddJavaTypes (javaTypes, td);
2636
}
2737
}
2838
}
2939

3040
return javaTypes;
3141
}
3242

33-
static void AddJavaTypes (List<TypeDefinition> javaTypes, TypeDefinition type, Action<string, object[]> log)
43+
void AddJavaTypes (List<TypeDefinition> javaTypes, TypeDefinition type)
3444
{
3545
if (type.IsSubclassOf ("Java.Lang.Object") || type.IsSubclassOf ("Java.Lang.Throwable")) {
3646

3747
// For subclasses of e.g. Android.App.Activity.
3848
javaTypes.Add (type);
3949
} else if (type.IsClass && !type.IsSubclassOf ("System.Exception") && type.ImplementsInterface ("Android.Runtime.IJavaObject")) {
40-
log (
41-
"Type '{0}' implements Android.Runtime.IJavaObject but does not inherit from Java.Lang.Object. It is not supported.",
42-
new [] { type.FullName });
50+
var level = ErrorOnCustomJavaObject ? TraceLevel.Error : TraceLevel.Warning;
51+
var prefix = ErrorOnCustomJavaObject ? "error" : "warning";
52+
Logger (
53+
level,
54+
$"{prefix} XA412: Type `{type.FullName}` implements `Android.Runtime.IJavaObject` but does not inherit `Java.Lang.Object` or `Java.Lang.Throwable`. This is not supported.");
4355
return;
4456
}
4557

4658
if (!type.HasNestedTypes)
4759
return;
4860

4961
foreach (TypeDefinition nested in type.NestedTypes)
50-
AddJavaTypes (javaTypes, nested, log);
62+
AddJavaTypes (javaTypes, nested);
5163
}
5264

5365
public static bool ShouldSkipJavaCallableWrapperGeneration (TypeDefinition type)
@@ -64,6 +76,11 @@ public static bool ShouldSkipJavaCallableWrapperGeneration (TypeDefinition type)
6476

6577
return false;
6678
}
67-
79+
// Returns all types for which we need to generate Java delegate types.
80+
public static List<TypeDefinition> GetJavaTypes (IEnumerable<string> assemblies, IAssemblyResolver resolver, Action<string, object []> log)
81+
{
82+
Action<TraceLevel, string> l = (level, value) => log ("{0}", new string [] { value });
83+
return new JavaTypeScanner (l).GetJavaTypes (assemblies, resolver);
84+
}
6885
}
6986
}

src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using System.IO;
34
using System.Collections.Generic;
45
using System.Linq;
@@ -56,18 +57,27 @@ namespace Java.Interop.Tools.JavaCallableWrappers {
5657
*/
5758
public class TypeNameMapGenerator : IDisposable {
5859

59-
Action<string, object[]> Log;
60+
Action<TraceLevel, string> Log;
6061
List<TypeDefinition> Types;
6162
DirectoryAssemblyResolver Resolver;
63+
JavaTypeScanner Scanner;
6264

65+
[Obsolete ("Use TypeNameMapGenerator(IEnumerable<string>, Action<TraceLevel, string>)")]
6366
public TypeNameMapGenerator (IEnumerable<string> assemblies, Action<string, object []> logMessage)
67+
: this (assemblies, (TraceLevel level, string value) => logMessage?.Invoke ("{0}", new[]{value}))
6468
{
65-
if (assemblies == null)
66-
throw new ArgumentNullException ("assemblies");
6769
if (logMessage == null)
6870
throw new ArgumentNullException (nameof (logMessage));
71+
}
6972

70-
Log = logMessage;
73+
public TypeNameMapGenerator (IEnumerable<string> assemblies, Action<TraceLevel, string> logger)
74+
{
75+
if (assemblies == null)
76+
throw new ArgumentNullException ("assemblies");
77+
if (logger == null)
78+
throw new ArgumentNullException (nameof (logger));
79+
80+
Log = logger;
7181
var Assemblies = assemblies.ToList ();
7282
var rp = new ReaderParameters ();
7383

@@ -83,17 +93,28 @@ public TypeNameMapGenerator (IEnumerable<string> assemblies, Action<string, obje
8393
Resolver.Load (Path.GetFullPath (assembly));
8494
}
8595

86-
Types = JavaTypeScanner.GetJavaTypes (Assemblies, Resolver, logMessage);
96+
Scanner = new JavaTypeScanner (Log) {
97+
ErrorOnCustomJavaObject = false,
98+
};
99+
Types = Scanner.GetJavaTypes (Assemblies, Resolver);
87100
}
88101

102+
[Obsolete ("Use TypeNameMapGenerator(IEnumerable<TypeDefinition>, Action<TraceLevel, string>)")]
89103
public TypeNameMapGenerator (IEnumerable<TypeDefinition> types, Action<string, object[]> logMessage)
104+
: this (types, (TraceLevel level, string value) => logMessage?.Invoke ("{0}", new [] { value }))
90105
{
91-
if (types == null)
92-
throw new ArgumentNullException ("types");
93106
if (logMessage == null)
94107
throw new ArgumentNullException (nameof (logMessage));
108+
}
109+
110+
public TypeNameMapGenerator (IEnumerable<TypeDefinition> types, Action<TraceLevel, string> logger)
111+
{
112+
if (types == null)
113+
throw new ArgumentNullException ("types");
114+
if (logger == null)
115+
throw new ArgumentNullException (nameof (logger));
95116

96-
Log = logMessage;
117+
Log = logger;
97118
Types = types.ToList ();
98119
}
99120

@@ -112,11 +133,6 @@ protected virtual void Dispose (bool disposing)
112133
Resolver = null;
113134
}
114135

115-
void LogWarning (string format, params object[] args)
116-
{
117-
Log (format, args);
118-
}
119-
120136
public void WriteJavaToManaged (Stream output)
121137
{
122138
if (output == null)
@@ -160,10 +176,10 @@ Dictionary<string, string> GetTypeMapping (Func<TypeDefinition, bool> skipType,
160176
}
161177
}
162178
foreach (var e in aliases.OrderBy (e => e.Key)) {
163-
LogWarning ("Mapping for type '{0}' is ambiguous between {1} types.", e.Key, e.Value.Count);
164-
LogWarning (" Using: {0}", e.Value.First ());
179+
Log (TraceLevel.Warning, $"Mapping for type '{e.Key}' is ambiguous between {e.Value.Count} types.");
180+
Log (TraceLevel.Warning, $" Using: {e.Value.First ()}");
165181
foreach (var o in e.Value.Skip (1)) {
166-
LogWarning (" Ignoring: {0}", o);
182+
Log (TraceLevel.Info, $" Ignoring: {o}");
167183
}
168184
}
169185
return typeMap.ToDictionary (e => e.Key, e => value (e.Value));

src/Java.Interop.Tools.JavaCallableWrappers/Test/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGeneratorTests.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.IO;
45
using System.Linq;
56
using System.Text;
@@ -9,6 +10,7 @@
910
using NUnit.Framework;
1011

1112
using Java.Interop.Tools.Cecil;
13+
using Java.Interop.Tools.Diagnostics;
1214
using Java.Interop.Tools.JavaCallableWrappers;
1315

1416
using Android.App;
@@ -24,16 +26,25 @@ public class TypeNameMapGeneratorTests
2426
public void ConstructorExceptions ()
2527
{
2628
Action<string, object []> logger = (f, o) => { };
27-
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator ((string [])null, logger));
28-
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator ((TypeDefinition [])null, logger));
29-
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator (new string [0], null));
30-
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator (new TypeDefinition [0], null));
29+
Action<string, object []> nullLogger = null;
30+
Action<TraceLevel, string> levelLogger = (l, v) => { };
31+
Action<TraceLevel, string> nullLevelLogger = null;
32+
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator ((string []) null, levelLogger));
33+
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator ((TypeDefinition []) null, levelLogger));
34+
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator (new string [0], nullLevelLogger));
35+
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator (new TypeDefinition [0], nullLevelLogger));
36+
#pragma warning disable 0618
37+
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator ((string []) null, logger));
38+
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator ((TypeDefinition []) null, logger));
39+
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator (new string [0], nullLogger));
40+
Assert.Throws<ArgumentNullException> (() => new TypeNameMapGenerator (new TypeDefinition [0], nullLogger));
41+
#pragma warning restore 0618
3142
}
3243

3344
[Test]
3445
public void WriteJavaToManaged ()
3546
{
36-
var v = new TypeNameMapGenerator (SupportDeclarations.GetTestTypeDefinitions (), logMessage: Console.WriteLine);
47+
var v = new TypeNameMapGenerator (SupportDeclarations.GetTestTypeDefinitions (), logger: Diagnostic.CreateConsoleLogger ());
3748
var o = new MemoryStream ();
3849
v.WriteJavaToManaged (o);
3950
var a = ToArray (o);
@@ -97,7 +108,7 @@ static string GetEntryPart (string value, int length)
97108
[Test]
98109
public void WriteManagedToJava ()
99110
{
100-
var v = new TypeNameMapGenerator (SupportDeclarations.GetTestTypeDefinitions (), logMessage: Console.WriteLine);
111+
var v = new TypeNameMapGenerator (SupportDeclarations.GetTestTypeDefinitions (), logger: Diagnostic.CreateConsoleLogger ());
101112
var o = new MemoryStream ();
102113
v.WriteManagedToJava (o);
103114
var a = ToArray (o);

src/Java.Interop/Documentation/Java.Interop/IJavaPeerable.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ partial class ExampleBinding {
314314
</member>
315315
<member name="M:SetPeerReference">
316316
<summary>Set the value returned by <c>PeerReference</c>.</summary>
317-
<param name="value">
317+
<param name="reference">
318318
A <see cref="T:Java.Interop.JniObjectReference" /> which contains the
319319
value that future invocations of the
320320
<see cref="P:Java.Interop.IJavaPeerable.PeerReference" />

tools/jcw-gen/App.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Linq;
34
using System.IO;
45
using System.Threading.Tasks;
56

67
using Java.Interop.Tools.Cecil;
8+
using Java.Interop.Tools.Diagnostics;
79
using Java.Interop.Tools.JavaCallableWrappers;
810
using Mono.Cecil;
911
using Mono.Options;
@@ -41,6 +43,7 @@ public static int Main (string [] args)
4143
"Show this message and exit",
4244
v => help = v != null },
4345
};
46+
var scanner = new JavaTypeScanner (Diagnostic.CreateConsoleLogger ());
4447
try {
4548
var assemblies = options.Parse (args);
4649
if (assemblies.Count == 0 || outputPath == null || help) {
@@ -60,7 +63,7 @@ public static int Main (string [] args)
6063
resolver.SearchDirectories.Add (Path.GetDirectoryName (assembly));
6164
resolver.Load (assembly);
6265
}
63-
var types = JavaTypeScanner.GetJavaTypes (assemblies, resolver, log: Console.WriteLine)
66+
var types = scanner.GetJavaTypes (assemblies, resolver)
6467
.Where (td => !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (td));
6568
foreach (var type in types) {
6669
GenerateJavaCallableWrapper (type, outputPath);

0 commit comments

Comments
 (0)