[Export] Fix dynamic export array callbacks#11428
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the legacy Mono.Android.Export dynamic callback generator to correctly marshal [Export] array parameters (including copy-back semantics), and re-enables the previously skipped array export tests so non-trimmable typemap paths are covered again.
Changes:
- Adjust callback array preparation to use
JNIEnv.GetArray<T>(IntPtr)and ensure prepared array parameters are materialized so copy-back updates the same managed instance passed to the exported method. - Fix copy-back to target the correct JNI argument slot and choose
JNIEnv.CopyArrayoverloads based on array element type. - Re-enable int-array and peer-array
[Export]device tests by removing the trimmable-only ignore gate.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ExportTests.cs | Removes the trimmable-only skip so array export tests run again on legacy export paths. |
| src/Mono.Android.Export/CallbackCode.cs | Fixes legacy dynamic callback array marshalling and copy-back codegen in Mono.Android.Export. |
Comments suppressed due to low confidence (1)
src/Mono.Android.Export/CallbackCode.cs:276
GetCallbackCleanup()'sCharSequencecase emits aJava.Lang.Object.GetObject<ICharSequence>(...)call during cleanup, but at the call siteargis the prepared managedICharSequencevalue (not the originalIntPtr). This will generate invalid IL / runtime failures for exported methods that takeICharSequenceparameters. Cleanup should be a no-op forCharSequenceparameters (e.g., returnarg/empty statement) rather than re-wrapping the managed object.
// CharSequenceSymbol:
// return new string[] { String.Format ("Java.Lang.ICharSequence {0} = Java.Lang.Object.GetObject<Java.Lang.ICharSequence> ({1}, JniHandleOwnership.DoNotTransfer);", var_name, SymbolTable.GetNativeName (var_name)) };
case SymbolKind.CharSequence:
return new CodeMethodCall (object_getobject.MakeGenericMethod (typeof (ICharSequence)), arg, do_not_transfer_literal);
Repair legacy Mono.Android.Export callback generation for array parameters and return values, and re-enable the array export tests on the stacked branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Report unsupported array copy-back types explicitly instead of letting a missing JNIEnv.CopyArray overload fail later during dynamic callback generation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
a3a8d77 to
eabf089
Compare
|
/review |
|
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
✅ LGTM — well-analyzed long-standing bug fix
Excellent detective work tracing this back to the original 2016 import. The fix is correct across all four dimensions:
- Prep: Calls the 1-arg generic
GetArray<T>(IntPtr)thatjnienv_getarrayactually caches — fixes the argument count mismatch. - Materialization:
DeclareVariableensures the same managed array instance is passed to the exported method and used for copy-back. - Cleanup slot:
mgen.GetArg(i + 2)correctly skipsJNIEnv*andjobjectto reach the original JNI array parameter. - Control flow:
CodeIfinstead ofCodeWhenavoids the invalid ternary that mixedint[]andvoidreturn types.
The CopyArray overload resolution fix (elementType instead of type, IJavaObject[] instead of IJavaObject) and the null guard on copyArrayMethod are good catches too.
Re-enabling the two array export tests on the legacy path is the right call now that the underlying code gen is fixed.
CI
- ✅
license/clapassed - ✅
dotnet-androidpassed - ⏳
Xamarin.Android-PR— not visible in checks (may need trigger or re-run)
Generated by Android PR Reviewer for issue #11428 · ● 19.1M
| @@ -740,7 +749,7 @@ CodeMethod GenerateNativeCallbackDelegate (string generatedMethodName) | |||
| // sw.WriteLine ("{0}\t{1}", indent, cleanup); | |||
| var callbackCleanup = new List<CodeStatement> (); | |||
There was a problem hiding this comment.
🤖 💡 Code organization — callbackCleanup is declared but never used (pre-existing). Now that this area is being touched, consider removing it to reduce noise.
Rule: Remove unused code
Stacked on #11123
Closes #1259
This PR keeps the trimmable typemap export PR focused on the new trimmable path, and moves the unrelated legacy
Mono.Android.Exportdynamic callback fixes here. The end result is that the new array[Export]tests can run head-to-head on the legacy non-trimmable typemap paths too, instead of being disabled there.Where this broke
I traced the
JNIEnv.GetArray<T>andMono.Android.Exporthistory. This was not a laterJNIEnv.GetArray<T>signature change that regressed working code. The mismatch was present from the originalMono.Android.Exportimport ind92a49d56([Mono.Android.Export] Import from monodroid/d393f359, 2016-04-21):CallbackCode.cscachedjnienv_getarrayfromJNIEnv.GetArray<int>, whose signature was alreadyGetArray<T>(IntPtr array_ptr).(IntPtr, JniHandleOwnership.DoNotTransfer, Type).So array-shaped dynamic
[Export]callbacks appear to have been broken since that import. The new tests in #11123 are what exposed the bug because they exercise[Export]methods with array parameters/returns on the legacy dynamic-export path.What used to crash
A small app/test like this is enough to trigger the broken legacy dynamic callback generation:
When Java/JNI calls
DoubleArray(int[]),Mono.Android.Exportdynamically emits a native callback delegate that converts the incomingjintArraytoint[], calls the managed method, converts the return value back to JNI, and copies any managed array parameter mutations back to the original JNI array.Before this fix, the generated callback was wrong in several ways. Simplified, it behaved like this:
Depending on exactly how far callback generation got, this could fail while creating the dynamic method (for example invalid
GetArray/CopyArraycalls or an invalid ternary expression mixingint[]andvoid) or later abort under CheckJNI because registration left a pending managed exception.With the fix, the generated shape is instead:
That fixes both the crash and the semantic bug: mutations to the managed
int[]parameter are copied back to the original JNI array handle.Changes
JNIEnv.GetArray<T>(IntPtr)method thatCallbackCodealready cached.JNIEnv.CopyArrayoverloads.ifstatement instead of an invalid ternary expression.Validation
Validation performed locally before splitting this out from #11123:
llvm-irIncludeCategories=Exportpassed with scalar/object/array tests enabled and exception-routing ignored.llvm-irIncludeCategories=Exportpassed with scalar/object/array tests enabled and exception-routing ignored.IncludeCategories=Exportpassed all export method tests.