Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit 0f1efeb

Browse files
authored
[Java.Interop] Use PublicApiAnalyzers to ensure we do not break API (#1170)
Fixes: #1169 Context: dotnet/android@76ab8b2dotnet/android@76ab8b2c mentions: > Now that we are in the .NET `TargetFramework` world we also need > to ensure we do not *add* any new API to a Target Framework once it > has shipped. A `TargetFramework` is essentially a contract that we > cannot change. (Imagine if you had different minor versions of .NET > on your local machine and CI machine, what works on one should work > on the other.) This prevents an issue where a user on `.NET 8.0.300` uses a method that isn't available to their coworker or CI on `.NET 8.0.100`. This logical argument also applies to `Java.Interop.dll`. Enable Microsoft's [PublicApiAnalyzers][0] for `Java.Interop.dll`. ([PublicApiAnalyzers documentation][1].) This ensures that we don't add new API once we've shipped `Java.Interop.dll` for a given .NET version. Update `build-tools/jnienv-gen` so that `JniEnvironment.g.cs` enables nullable reference types. This allows us to *avoid* disabling RS0041. Co-authored-by: Jonathan Pryor <jonpryor@vt.edu> [0]: https://github.com/dotnet/roslyn-analyzers/tree/ace28a1039d09626a94d3b0f8ce69e547ca2bcbf/src/PublicApiAnalyzers [1]: https://github.com/dotnet/roslyn-analyzers/blob/ace28a1039d09626a94d3b0f8ce69e547ca2bcbf/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md
1 parent 8b85462 commit 0f1efeb

8 files changed

Lines changed: 2017 additions & 1090 deletions

File tree

‎build-tools/jnienv-gen/Generator.cs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ static void GenerateFile (TextWriter o)
8686
o.WriteLine("// Generated file; DO NOT EDIT!");
8787
o.WriteLine("//");
8888
o.WriteLine("// To make changes, edit monodroid/tools/jnienv-gen-interop and rerun");
89+
o.WriteLine("#nullable enable");
8990
o.WriteLine();
9091
o.WriteLine("#if !FEATURE_JNIENVIRONMENT_SAFEHANDLES && !FEATURE_JNIENVIRONMENT_JI_INTPTRS && !FEATURE_JNIENVIRONMENT_JI_PINVOKES && !FEATURE_JNIENVIRONMENT_XA_INTPTRS && !FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS");
9192
o.WriteLine("#define FEATURE_JNIENVIRONMENT_JI_PINVOKES");
@@ -255,7 +256,7 @@ static void GenerateJniNativeInterfaceInvoker (TextWriter o, HandleStyle style)
255256
if(e.Prebind)
256257
o.WriteLine("\t\tpublic readonly {0} {1};\n",d,e.Name);
257258
else{
258-
o.WriteLine("\t\t{0} _{1};",d,e.Name);
259+
o.WriteLine("\t\t{0}? _{1};",d,e.Name);
259260
o.WriteLine("\t\tpublic {0} {1} {{",d,e.Name);
260261
o.WriteLine("\t\t\tget {");
261262
o.WriteLine("\t\t\t\tif (_{0} == null)\n\t\t\t\t\t{1}",e.Name,Initialize(e,"_",d));
@@ -551,7 +552,7 @@ static void RaiseException (TextWriter o, JniFunction entry, HandleStyle style)
551552
return;
552553

553554
o.WriteLine();
554-
o.WriteLine("\t\t\tException __e = JniEnvironment.GetExceptionForLastThrowable ({0});",
555+
o.WriteLine("\t\t\tException? __e = JniEnvironment.GetExceptionForLastThrowable ({0});",
555556
(style==HandleStyle.JIIntPtrPinvokeWithErrors||style==HandleStyle.JIFunctionPtrWithErrors)
556557
?"thrown"
557558
:"");
@@ -1127,7 +1128,7 @@ public override string[] GetMarshalToManagedStatements (HandleStyle style, strin
11271128
caseHandleStyle.JIFunctionPtrWithErrors:
11281129
returnnew[]{
11291130
string.Format("if ({0} == IntPtr.Zero)",variable),
1130-
string.Format("\treturn null;"),
1131+
string.Format($"\tthrow new InvalidOperationException (\"Should not be reached; `{entry.Name}` should have thrown!\");"),
11311132
string.Format("return new {0} ({1}, {2}, {3}, isStatic: {4});",type,entry.Parameters[1].Name,entry.Parameters[2].Name,variable,IsStatic?"true":"false"),
11321133
};
11331134
caseHandleStyle.XAIntPtr:

‎src/Java.Interop/Java.Interop.csproj‎

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,11 @@
8888
<DependentUpon>JniPeerMembers.JniInstanceMethods_Invoke.tt</DependentUpon>
8989
</Compile>
9090
</ItemGroup>
91-
<ProjectExtensions>
92-
<MonoDevelop>
93-
<Properties>
94-
<Policies>
95-
<VersionControlPolicy>
96-
<CommitMessageStyleIndent="&#x9;"LineAlign="0"IncludeDirectoryPaths="True" />
97-
</VersionControlPolicy>
98-
</Policies>
99-
</Properties>
100-
</MonoDevelop>
101-
</ProjectExtensions>
10291
<ItemGroup>
92+
<PackageReferenceInclude="Microsoft.CodeAnalysis.PublicApiAnalyzers"Version="3.3.4">
93+
<PrivateAssets>all</PrivateAssets>
94+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
95+
</PackageReference>
10396
<PackageReferenceInclude="Microsoft.SourceLink.GitHub"PrivateAssets="All" />
10497
</ItemGroup>
10598
<ImportProject="Java.Interop.targets" />

‎src/Java.Interop/Java.Interop/JavaObjectArray.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class JavaObjectArray<T> : JavaArray<T>
1111
{
1212
internalstaticreadonlyValueMarshalerInstance=newValueMarshaler();
1313

14+
[SuppressMessage("ApiDesign","RS0022:Constructor make noninheritable base class inheritable",Justification="Existing public API")]
1415
publicJavaObjectArray(refJniObjectReferencehandle,JniObjectReferenceOptionsoptions)
1516
:base(refhandle,options)
1617
{
@@ -29,20 +30,23 @@ static JniObjectReference NewArray (int length)
2930
}
3031
}
3132

33+
[SuppressMessage("ApiDesign","RS0022:Constructor make noninheritable base class inheritable",Justification="Existing public API")]
3234
publicunsafeJavaObjectArray(intlength)
3335
:this(ref*InvalidJniObjectReference,JniObjectReferenceOptions.None)
3436
{
3537
varpeer=NewArray(CheckLength(length));
3638
Construct(refpeer,JniObjectReferenceOptions.CopyAndDispose);
3739
}
3840

41+
[SuppressMessage("ApiDesign","RS0022:Constructor make noninheritable base class inheritable",Justification="Existing public API")]
3942
publicJavaObjectArray(IList<T>value)
4043
:this(CheckLength(value))
4144
{
4245
for(inti=0;i<value.Count;++i)
4346
SetElementAt(i,value[i]);
4447
}
4548

49+
[SuppressMessage("ApiDesign","RS0022:Constructor make noninheritable base class inheritable",Justification="Existing public API")]
4650
publicJavaObjectArray(IEnumerable<T>value)
4751
:this(ToList(value))
4852
{

‎src/Java.Interop/Java.Interop/JniEnvironment.Types.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
262262
}
263263
#endif // DEBUG && NETCOREAPP
264264

265-
intr=_RegisterNatives(type,methods,numMethods);
265+
intr=_RegisterNatives(type,methods??Array.Empty<JniNativeMethodRegistration>(),numMethods);
266266

267267
if(r!=0){
268268
thrownewInvalidOperationException(

‎src/Java.Interop/Java.Interop/JniValueMarshaler.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public struct JniValueMarshalerState : IEquatable<JniValueMarshalerState> {
4949
publicIJavaPeerable?PeerableValue{get;privateset;}
5050
publicobject?Extra{get;privateset;}
5151

52+
[SuppressMessage("ApiDesign","RS0026:Do not add multiple public overloads with optional parameters",Justification="Existing public API")]
5253
publicJniValueMarshalerState(JniArgumentValuejniArgumentValue,object?extra=null)
5354
{
5455
JniArgumentValue=jniArgumentValue;
@@ -57,6 +58,7 @@ public JniValueMarshalerState (JniArgumentValue jniArgumentValue, object? extra
5758
Extra=extra;
5859
}
5960

61+
[SuppressMessage("ApiDesign","RS0026:Do not add multiple public overloads with optional parameters",Justification="Existing public API")]
6062
publicJniValueMarshalerState(JniObjectReferencereferenceValue,object?extra=null)
6163
{
6264
JniArgumentValue=newJniArgumentValue(referenceValue);
@@ -65,6 +67,7 @@ public JniValueMarshalerState (JniObjectReference referenceValue, object? extra
6567
Extra=extra;
6668
}
6769

70+
[SuppressMessage("ApiDesign","RS0026:Do not add multiple public overloads with optional parameters",Justification="Existing public API")]
6871
publicJniValueMarshalerState(IJavaPeerable?peerableValue,object?extra=null)
6972
{
7073
PeerableValue=peerableValue;

0 commit comments

Comments
 (0)