镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions Documentation/Java.Interop/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ Raw callback entry points and marshal targets require the registered ABI,
current-thread JNI frame and live compatible borrowed references without
concurrent deletion. Their `<safety>` contracts do not validate raw addresses.
Typed managed targets stay safe. Generated syntax and the existing legacy
compiler test harness are preserved; Mono.Android and customer projects do not
opt into the updated memory-safety rules.
compiler test harness are preserved. Mono.Android's opt-in is project-scoped;
customer projects do not opt in implicitly.

Throwable bindings forward `JniObjectReferenceOptions.None` through internal,
unregistered empty-peer constructors, preserving partial hooks and normal JNI
Expand All @@ -182,12 +182,24 @@ initialization. An updated-rules external raw base can therefore still reject a
safe derived initializer; internal forwarding does not solve that separate
cross-assembly enforcement boundary.

Non-Throwable raw constructors, including class/interface invokers, construct
JNI peers, not opaque-address containers. Their documented handle type, lifetime,
thread, aliasing, ownership and failure duties still apply. `RequiresUnsafe`
enforcement is explicitly deferred to preserve safe zero/`DoNotTransfer` base
initializers, without adding a general initialization API or weakening the
approved Throwable path.
Released Mono.Android raw-handle constructors, including `Java.Lang.Object`,
`Java.Lang.Throwable`, generated forwarding constructors and class/interface
invokers, remain safe-callable as explicit compatibility exceptions. They construct
JNI peers, not opaque-address containers. The compiler does not enforce their
documented handle type, lifetime, thread, aliasing, ownership or failure duties,
including for nonzero handles. These exceptions do not validate JNI references.
They preserve existing zero/`DoNotTransfer` base initializers without adding a
general initialization API, changing the Throwable internal `None` route, or
relaxing Java.Interop's raw VM, reference, option, buffer or callback contracts.

Mono.Android's existing public JNI and typed marshal methods likewise preserve
their public member metadata without adding `RequiresUnsafeAttribute`.
Implementation-only operations remain inside local unsafe contexts. Under the
updated rules, pointer and function-pointer parameters no longer require a
checked caller to enter an unsafe context for these methods. This broadens
compiler-accepted calls; it does not validate or remove the documented JNI
reference, buffer, ABI, thread, ownership or lifetime obligations. Java.Interop's
independently enforced raw contracts are unchanged.

## Naming conventions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,37 @@ Specifies a name for an event. If the name is empty, it prevents event generatio
This is described in more detail in the section
[Renaming EventArg Wrapper Classes](#Renaming_EventArg_Wrapper_Classes).

### eventDefaultReturn

Specifies the C# return expression used by a generated non-void listener delegate
when no managed handler is installed. The expression can use the listener
method's managed parameter names and must match its return type and nullability.
Without this attribute, the generated implementation returns the type's default
value. This does not change `EventHandler` projections that use a `Handled`
property.

The value is copied verbatim into generated C#, without expression-only
validation or sanitization. Calls can have side effects, and crafted text can
inject additional C# source. Treat metadata containing this attribute as trusted
source code, not as untrusted runtime configuration. Review third-party metadata
as carefully as hand-written binding code. Keep fallback expressions small; use a
hand-written listener implementation for more complex behavior.

For an ordinary return expression, evaluation occurs when the listener method is
called without a managed handler, not during metadata processing or source
generation. Invalid generated C# produces normal compiler diagnostics; runtime
exceptions follow the usual managed-call or JNI callback exception handling.

For example, retain a view's normal window-insets behavior when no handler is set:

```xml
<attr path="/api/package[@name='android.view']/interface[@name='View.OnApplyWindowInsetsListener']/method[@name='onApplyWindowInsets']"
name="eventDefaultReturn">v.OnApplyWindowInsets (insets) ?? throw new global::Java.Lang.NullPointerException ()</attr>
```
Comment on lines +258 to +261

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, they can insert a random block of code here?

Is that too "wide open" for developers to do weird things?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes—this is effectively a C# source escape hatch, not a sandbox. The writer inserts the value verbatim after the conditional's :. A bare statement block isn't the intended input, but there is no expression-only validation: calls can have side effects, and crafted text can inject additional source. Calling it an “expression” didn't make that limitation clear.

The input is binding-build metadata (Transforms/Metadata.xml, or the supplied API XML), not text interpreted from Java callbacks at runtime. Existing customAttributes metadata also reaches a raw source writer, and binding authors can add hand-written C#. That explains the existing trust model; it does not make arbitrary third-party metadata safe to accept.

I kept the hook's behavior and clarified the docs in 552df45: treat this metadata as source code, review third-party inputs accordingly, keep fallbacks small, and implement a listener by hand for complex behavior. Restricting this to constants/parameter names would lose the window-insets fallback's platform-method call; merely parsing an expression wouldn't prevent that call from having side effects.

For an ordinary fallback expression, it runs only when the listener method is called without a handler—not during generation. Invalid generated C# produces normal compiler diagnostics; exceptions follow the ordinary managed/JNI callback path. This commit changes documentation only; callback/nullability/public API/default-return behavior is unchanged.

Auto-replied by the GitHub Copilot app

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a design we could come up with where they WOULDN'T stuff a block of C# code here?!?

Can we suggest using a partial class declaration instead?

You might check what we'd do for this case currently:


The null check preserves the listener's non-null return contract without changing
the view method's existing nullable managed signature.

### managedName

This is used to change the name of a package, class, method, or
Expand Down
12 changes: 9 additions & 3 deletions build-tools/jnienv-gen/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ static void GenerateJniEnv (TextWriter o)
case "CopyArray":
WriteSafetyRequirements (o, entry.CallerSafetyRequirements);
o.Write ("\t\t{2} static {3}{0} {1} (", entry.ReturnType.ManagedType, entry.ApiName, entry.Visibility,
entry.RequiresUnsafe ? "unsafe " : "");
RequiresUnsafeDeclaration (entry) ? "unsafe " : "");
if (entry.Name.StartsWith ("G", StringComparison.Ordinal)) {
o.WriteLine ("IntPtr src, {0} dest)", entry.Parameters [3].Type.ManagedType);
o.WriteLine ("\t\t{");
Expand Down Expand Up @@ -182,7 +182,7 @@ entry.Parameters [entry.Parameters.Length-1].IsParamArray &&
entry.Visibility,
entry.ReturnType.ManagedType,
entry.ApiName,
entry.RequiresUnsafe ? "unsafe " : "");
RequiresUnsafeDeclaration (entry) ? "unsafe " : "");
bool is_void = entry.ReturnType.ManagedType == "void";
WriteJniFunctionParameters (o, entry, generateParamsOverload);
o.WriteLine ("\t\t{");
Expand Down Expand Up @@ -242,7 +242,7 @@ entry.Parameters [entry.Parameters.Length-1].IsParamArray &&
o.WriteLine ();
WriteSafetyRequirements (o, entry.CallerSafetyRequirements);
o.Write ("\t\t{0} static {1}{2} {3} ",
entry.Visibility, entry.RequiresUnsafe ? "unsafe " : "", entry.ReturnType.ManagedType, entry.ApiName);
entry.Visibility, RequiresUnsafeDeclaration (entry) ? "unsafe " : "", entry.ReturnType.ManagedType, entry.ApiName);
WriteJniFunctionParameters (o, entry, false);
o.WriteLine ("\t\t{");
o.WriteLine ("\t\t\tunsafe {");
Expand All @@ -257,6 +257,12 @@ entry.Parameters [entry.Parameters.Length-1].IsParamArray &&
o.WriteLine ("\t\t}");
}

static bool RequiresUnsafeDeclaration (JniFunction entry)
{
// Preserve the public metadata; JNI preconditions remain documented and implementation contexts stay local.
return entry.RequiresUnsafe && entry.Visibility != "public";
}

static void WriteSafetyRequirements (TextWriter o, string [] requirements)
{
if (requirements.Length == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ public sealed partial class Builder
// These methods were obsoleted as a warning in API-31
[global::System.Obsolete ("This method has the wrong enumeration. Use the version that takes an 'Android.Bluetooth.BluetoothPhy' instead.")]
[global::System.Runtime.Versioning.SupportedOSPlatformAttribute ("android26.0")]
public unsafe Android.Bluetooth.LE.AdvertisingSetParameters.Builder? SetPrimaryPhy ([global::Android.Runtime.GeneratedEnum] Android.Bluetooth.LE.ScanSettingsPhy primaryPhy)
public Android.Bluetooth.LE.AdvertisingSetParameters.Builder? SetPrimaryPhy ([global::Android.Runtime.GeneratedEnum] Android.Bluetooth.LE.ScanSettingsPhy primaryPhy)
=> SetPrimaryPhy ((Android.Bluetooth.BluetoothPhy) primaryPhy);

[global::System.Obsolete ("This method has the wrong enumeration. Use the version that takes an 'Android.Bluetooth.BluetoothPhy' instead.")]
[global::System.Runtime.Versioning.SupportedOSPlatformAttribute ("android26.0")]
public unsafe Android.Bluetooth.LE.AdvertisingSetParameters.Builder? SetSecondaryPhy ([global::Android.Runtime.GeneratedEnum] Android.Bluetooth.LE.ScanSettingsPhy secondaryPhy)
public Android.Bluetooth.LE.AdvertisingSetParameters.Builder? SetSecondaryPhy ([global::Android.Runtime.GeneratedEnum] Android.Bluetooth.LE.ScanSettingsPhy secondaryPhy)
=> SetSecondaryPhy ((Android.Bluetooth.BluetoothPhy) secondaryPhy);
}
}
}

#endif

5 changes: 2 additions & 3 deletions src/Mono.Android/Android.Bluetooth/BluetoothDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ public sealed partial class BluetoothDevice
[global::System.Obsolete ("This method has the wrong enumeration. Use the version that takes an 'Android.Bluetooth.BluetoothPhy' instead.")]
[global::System.Runtime.Versioning.ObsoletedOSPlatformAttribute ("android37.0")]
[global::System.Runtime.Versioning.SupportedOSPlatformAttribute ("android26.0")]
public unsafe Android.Bluetooth.BluetoothGatt? ConnectGatt (Android.Content.Context? context, bool autoConnect, Android.Bluetooth.BluetoothGattCallback? @callback, [global::Android.Runtime.GeneratedEnum] Android.Bluetooth.BluetoothTransports transport, [global::Android.Runtime.GeneratedEnum] Android.Bluetooth.LE.ScanSettingsPhy phy)
public Android.Bluetooth.BluetoothGatt? ConnectGatt (Android.Content.Context? context, bool autoConnect, Android.Bluetooth.BluetoothGattCallback? @callback, [global::Android.Runtime.GeneratedEnum] Android.Bluetooth.BluetoothTransports transport, [global::Android.Runtime.GeneratedEnum] Android.Bluetooth.LE.ScanSettingsPhy phy)
=> ConnectGatt (context, autoConnect, @callback, transport, (Android.Bluetooth.BluetoothPhy) phy);

[global::System.Obsolete ("This method has the wrong enumeration. Use the version that takes an 'Android.Bluetooth.BluetoothPhy' instead.")]
[global::System.Runtime.Versioning.ObsoletedOSPlatformAttribute ("android37.0")]
[global::System.Runtime.Versioning.SupportedOSPlatformAttribute ("android26.0")]
public unsafe Android.Bluetooth.BluetoothGatt? ConnectGatt (Android.Content.Context? context, bool autoConnect, Android.Bluetooth.BluetoothGattCallback? @callback, [global::Android.Runtime.GeneratedEnum] Android.Bluetooth.BluetoothTransports transport, [global::Android.Runtime.GeneratedEnum] Android.Bluetooth.LE.ScanSettingsPhy phy, Android.OS.Handler? handler)
public Android.Bluetooth.BluetoothGatt? ConnectGatt (Android.Content.Context? context, bool autoConnect, Android.Bluetooth.BluetoothGattCallback? @callback, [global::Android.Runtime.GeneratedEnum] Android.Bluetooth.BluetoothTransports transport, [global::Android.Runtime.GeneratedEnum] Android.Bluetooth.LE.ScanSettingsPhy phy, Android.OS.Handler? handler)
=> ConnectGatt (context, autoConnect, @callback, transport, (Android.Bluetooth.BluetoothPhy) phy, handler);
}
}

#endif

2 changes: 1 addition & 1 deletion src/Mono.Android/Android.Graphics/Bitmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public IntPtr LockPixels ()
/// aliases before unlocking, and exclude concurrent disposal or another unlock until this call returns
/// or throws. An exception does not prove that previously borrowed pixel addresses remain usable.
/// </safety>
public unsafe void UnlockPixels ()
public void UnlockPixels ()
{
unsafe {
// SAFETY: the caller owns this Bitmap's matching lock and has stopped pixel-alias access;
Expand Down
Loading