[JEWEL-1388] Stop using sun.misc.Unsafe to resolve the native window handle

closes https://github.com/JetBrains/intellij-community/pull/3604


(cherry picked from commit 5535ed3dcc6d84e420299a8f4b9a3b7481d34a10)

IJ-MR-220560

GitOrigin-RevId: 0857256456bb959888af8ce85f45fd5645eb7f51
This commit is contained in:
Wellington Pereira
2026-09-07 22:29:10 +00:00
committed by intellij-monorepo-bot
parent 0b3921720a
commit 45bb50bd91
7 changed files with 100 additions and 240 deletions
@@ -66,6 +66,10 @@ jvm_library(
"//libraries/kotlinx/coroutines/test:test_test_lib",
"//platform/jewel/ui",
"//platform/jewel/ui:ui_test_lib",
"//libraries/compose-foundation-desktop",
"//libraries/compose-foundation-desktop:compose-foundation-desktop_test_lib",
"//libraries/jna",
"//libraries/jna:jna_test_lib",
],
)
### auto-generated section `build intellij.platform.jewel.intUi.standalone.tests` end
@@ -1,4 +1,8 @@
plugins { jewel }
plugins {
jewel
alias(libs.plugins.composeDesktop)
alias(libs.plugins.compose.compiler)
}
dependencies {
api(projects.intUi.intUiStandalone)
@@ -9,6 +13,8 @@ dependencies {
testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.kotlinx.coroutines.core)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") }
testImplementation(libs.jna.core)
}
tasks.test { useJUnitPlatform() }
@@ -39,5 +39,7 @@
<orderEntry type="module" module-name="intellij.libraries.kotlinx.coroutines.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.kotlinx.coroutines.test" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.jewel.ui" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.compose.foundation.desktop" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.jna" scope="TEST" />
</component>
</module>
@@ -1,71 +0,0 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.window.macos
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import org.junit.jupiter.api.Test
/**
* Regression test for JEWEL-1387: the reflective lookups used to resolve the native window must search the whole class
* hierarchy, not just the runtime class.
*
* On JBR 25 the AWT window peer is `sun.lwawt.macosx.LWCWindowPeer`, a subclass of `sun.lwawt.LWWindowPeer` that
* inherits (but does not declare) `getPlatformWindow()`. A `getDeclaredMethod` call on the runtime class alone throws
* [NoSuchMethodException] and silently breaks native window chrome updates on macOS.
*/
internal class MacPlatformServicesReflectionTest {
@Suppress("UnusedPrivateMember", "FunctionOnlyReturningConstant")
private open class BasePeer {
@JvmField internal val ptr: Long = 42L
fun getPlatformWindow(): Any = this
}
// Mimics JBR 25's LWCWindowPeer: inherits getPlatformWindow() and ptr without declaring them
private class SubclassPeer : BasePeer()
@Test
fun `findMethodInHierarchy finds method declared on the class itself`() {
val method = MacPlatformServicesDefaultImpl.findMethodInHierarchy(BasePeer::class.java, "getPlatformWindow")
assertNotNull(method, "Should find a method declared directly on the class")
}
@Test
fun `findMethodInHierarchy finds method inherited from a superclass`() {
val method = MacPlatformServicesDefaultImpl.findMethodInHierarchy(SubclassPeer::class.java, "getPlatformWindow")
assertNotNull(method, "Should find a method declared on a superclass (JBR 25 LWCWindowPeer scenario)")
assertEquals(BasePeer::class.java, method.declaringClass, "Method should be resolved from the superclass")
}
@Test
fun `findMethodInHierarchy returns null for a missing method`() {
val method = MacPlatformServicesDefaultImpl.findMethodInHierarchy(SubclassPeer::class.java, "doesNotExist")
assertNull(method, "Should return null instead of throwing for missing methods")
}
@Test
fun `findFieldInHierarchy finds field declared on the class itself`() {
val field = MacPlatformServicesDefaultImpl.findFieldInHierarchy(BasePeer::class.java, "ptr")
assertNotNull(field, "Should find a field declared directly on the class")
}
@Test
fun `findFieldInHierarchy finds field inherited from a superclass`() {
val field = MacPlatformServicesDefaultImpl.findFieldInHierarchy(SubclassPeer::class.java, "ptr")
assertNotNull(field, "Should find a field declared on a superclass")
assertEquals(BasePeer::class.java, field.declaringClass, "Field should be resolved from the superclass")
}
@Test
fun `findFieldInHierarchy returns null for a missing field`() {
val field = MacPlatformServicesDefaultImpl.findFieldInHierarchy(SubclassPeer::class.java, "doesNotExist")
assertNull(field, "Should return null instead of throwing for missing fields")
}
}
@@ -0,0 +1,55 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.window.macos
import androidx.compose.ui.awt.ComposeWindow
import javax.swing.JFrame
import javax.swing.SwingUtilities
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.DisabledIf
/**
* Regression test for JEWEL-1388: resolving the native `NSWindow*` must not require `sun.misc.Unsafe` or reflection
* into JDK-internal `sun.awt`/`sun.lwawt.macosx` classes.
*/
internal class MacPlatformServicesWindowHandleTest {
@Test
fun `getWindowFromJavaWindow returns NIL for a null window`() {
assertEquals(ID.NIL, MacPlatformServicesDefaultImpl.getWindowFromJavaWindow(null))
}
@Test
@DisabledIf(HEADLESS)
fun `getWindowFromJavaWindow returns NIL for a non-Compose AWT window`() {
val frame = JFrame()
try {
assertEquals(ID.NIL, MacPlatformServicesDefaultImpl.getWindowFromJavaWindow(frame))
} finally {
frame.dispose()
}
}
@Test
@DisabledIf(HEADLESS)
fun `getWindowFromJavaWindow returns the native handle of a realized ComposeWindow`() {
lateinit var window: ComposeWindow
try {
var handle = 0L
SwingUtilities.invokeAndWait {
window = ComposeWindow()
window.isVisible = true
handle = window.windowHandle
}
assertNotEquals(0L, handle, "A displayed ComposeWindow must expose a native window handle")
assertEquals(ID(handle), MacPlatformServicesDefaultImpl.getWindowFromJavaWindow(window))
} finally {
SwingUtilities.invokeAndWait { window.dispose() }
}
}
private companion object {
const val HEADLESS = "java.awt.GraphicsEnvironment#isHeadless"
}
}
@@ -1,69 +0,0 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.intui.standalone.window
import java.lang.reflect.AccessibleObject
import java.util.logging.Level
import java.util.logging.Logger
import sun.misc.Unsafe
internal object UnsafeAccessing {
private val logger = Logger.getLogger(UnsafeAccessing::class.java.simpleName)
private val unsafe: Any? by lazy {
try {
val theUnsafe = Unsafe::class.java.getDeclaredField("theUnsafe")
theUnsafe.isAccessible = true
theUnsafe.get(null) as Unsafe
} catch (@Suppress("TooGenericExceptionCaught") error: Throwable) {
logger.log(Level.WARNING, "Unsafe accessing initializing failed.", error)
null
}
}
val desktopModule by lazy { ModuleLayer.boot().findModule("java.desktop").get() }
val ownerModule: Module by lazy { this.javaClass.module }
private val isAccessibleFieldOffset: Long? by lazy {
try {
@Suppress("DEPRECATION")
(unsafe as? Unsafe)?.objectFieldOffset(Parent::class.java.getDeclaredField("first"))
} catch (_: Throwable) {
null
}
}
private val implAddOpens by lazy {
try {
Module::class.java.getDeclaredMethod("implAddOpens", String::class.java, Module::class.java).accessible()
} catch (_: Throwable) {
null
}
}
fun assignAccessibility(obj: AccessibleObject) {
try {
val theUnsafe = unsafe as? Unsafe ?: return
val offset = isAccessibleFieldOffset ?: return
theUnsafe.putBooleanVolatile(obj, offset, true)
} catch (_: Throwable) {
// ignore
}
}
fun assignAccessibility(module: Module, packages: List<String>) {
try {
packages.forEach { implAddOpens?.invoke(module, it, ownerModule) }
} catch (_: Throwable) {
// ignore
}
}
private class Parent {
var first = false
@Volatile var second: Any? = null
}
}
internal fun <T : AccessibleObject> T.accessible(): T = apply { UnsafeAccessing.assignAccessibility(this) }
@@ -3,13 +3,10 @@ package org.jetbrains.jewel.intui.standalone.window.macos
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.awt.ComposeWindow
import com.sun.jna.Callback
import com.sun.jna.Pointer
import java.awt.Component
import java.awt.Window
import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import javax.swing.SwingUtilities
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.VisibleForTesting
@@ -17,132 +14,63 @@ import org.jetbrains.jewel.foundation.InternalJewelApi
import org.jetbrains.jewel.foundation.util.JewelLogger
import org.jetbrains.jewel.intui.standalone.styling.default
import org.jetbrains.jewel.intui.standalone.styling.macOs
import org.jetbrains.jewel.intui.standalone.window.UnsafeAccessing
import org.jetbrains.jewel.intui.standalone.window.accessible
import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility
import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior
import org.jetbrains.skiko.hostOs
/**
* Provides macOS-specific platform services for window decoration and system preference observation.
*
* Implementations communicate with macOS APIs (via JNA/Objective-C) to apply native window chrome updates and read
* scrollbar settings from native sources such as NSUserDefaults (track-click behavior) and NSScroller (scroller style)
*/
@ApiStatus.Internal
@InternalJewelApi
public interface MacPlatformServices {
/** Updates the native window's color scheme to match the current Jewel theme. */
public fun updateColors(w: Window)
/** Refreshes the full-screen button state in the native window title bar. */
public fun updateFullScreenButtons(w: Window)
/** Hides the system cursor until the next mouse-move event (delegates to `NSCursor`). */
public fun hideCursorUntilMoved()
/** Reads the current scrollbar track-click behavior from `NSUserDefaults`. */
public fun readScrollbarTrackClickBehavior(): TrackClickBehavior
/** Reads the current scrollbar visibility style from the native `NSScroller` preference. */
public fun readScrollbarVisibility(): ScrollbarVisibility
/**
* Registers [action] to be invoked whenever a scrollbar-related system preference changes.
*
* Observes both `NSPreferredScrollerStyleDidChangeNotification` (visibility) and
* `AppleNoRedisplayAppearancePreferenceChanged` (track-click behavior).
*/
public fun onPreferencesChanged(action: () -> Unit)
}
/** Default [MacPlatformServices] implementation that uses JNA to invoke native macOS (Objective-C) APIs. */
@ApiStatus.Internal
@InternalJewelApi
public object MacPlatformServicesDefaultImpl : MacPlatformServices {
private val logger = JewelLogger.getInstance(MacPlatformServicesDefaultImpl::class.java.simpleName)
private var nativeCallbackReference: Callback? = null // Keep a strong reference here to prevent GC
init {
try {
UnsafeAccessing.assignAccessibility(
UnsafeAccessing.desktopModule,
listOf("sun.awt", "sun.lwawt", "sun.lwawt.macosx"),
)
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
logger.warn("Assign access for jdk.desktop failed.", e)
}
}
internal fun getWindowFromJavaWindow(w: Window?): ID {
if (w == null) {
return ID.NIL
}
try {
val cPlatformWindow = getPlatformWindow(w)
if (cPlatformWindow != null) {
val ptr = findFieldInHierarchy(cPlatformWindow.javaClass, "ptr")
if (ptr == null) {
logger.warn("Fail to get cPlatformWindow from awt window: no 'ptr' field found.")
return ID.NIL
}
ptr.setAccessible(true)
return ID(ptr.getLong(cPlatformWindow))
}
} catch (e: IllegalAccessException) {
logger.warn("Fail to get cPlatformWindow from awt window.", e)
}
return ID.NIL
}
public fun getPlatformWindow(w: Window): Any? {
try {
val awtAccessor = Class.forName("sun.awt.AWTAccessor")
val componentAccessor = awtAccessor.getMethod("getComponentAccessor").invoke(null)
val getPeer = componentAccessor.javaClass.getMethod("getPeer", Component::class.java).accessible()
val peer = getPeer.invoke(componentAccessor, w)
if (peer != null) {
// The method may be declared on a superclass of the runtime peer class (e.g., on JBR 25 the peer
// is sun.lwawt.macosx.LWCWindowPeer, which inherits getPlatformWindow() from sun.lwawt.LWWindowPeer),
// so we must search the whole class hierarchy rather than just the runtime class.
val getPlatformWindowMethod = findMethodInHierarchy(peer.javaClass, "getPlatformWindow")
if (getPlatformWindowMethod == null) {
logger.warn("Fail to get cPlatformWindow from awt window: no getPlatformWindow() method found.")
return null
}
val cPlatformWindow = getPlatformWindowMethod.invoke(peer)
if (cPlatformWindow != null) {
return cPlatformWindow
}
}
} catch (e: IllegalAccessException) {
logger.warn("Fail to get cPlatformWindow from awt window.", e)
} catch (e: InvocationTargetException) {
logger.warn("Fail to get cPlatformWindow from awt window.", e)
} catch (e: ClassNotFoundException) {
logger.warn("Fail to get cPlatformWindow from awt window.", e)
}
return null
}
/**
* Finds a method with the given [name] and no parameters declared on [clazz] or any of its superclasses, or `null`
* if no such method exists.
* Returns the native `NSWindow*` backing the given AWT [Window], or [ID.NIL] if [w] isn't a [ComposeWindow] or the
* native surface hasn't been realized yet.
*
* Uses [ComposeWindow.windowHandle], Compose's own supported accessor, instead of reflecting into JDK-internal
* `sun.awt`/`sun.lwawt.macosx` classes (see JEWEL-1388).
*/
@VisibleForTesting
@ApiStatus.Internal
@InternalJewelApi
public fun findMethodInHierarchy(clazz: Class<*>, name: String): Method? {
var current: Class<*>? = clazz
while (current != null) {
try {
return current.getDeclaredMethod(name)
} catch (_: NoSuchMethodException) {
current = current.superclass
}
}
return null
}
/**
* Finds a field with the given [name] declared on [clazz] or any of its superclasses, or `null` if no such field
* exists.
*/
@VisibleForTesting
@ApiStatus.Internal
@InternalJewelApi
public fun findFieldInHierarchy(clazz: Class<*>, name: String): Field? {
var current: Class<*>? = clazz
while (current != null) {
try {
return current.getDeclaredField(name)
} catch (_: NoSuchFieldException) {
current = current.superclass
}
}
return null
public fun getWindowFromJavaWindow(w: Window?): ID {
val handle = (w as? ComposeWindow)?.windowHandle ?: 0L
return if (handle == 0L) ID.NIL else ID(handle)
}
public override fun updateColors(w: Window) {
@@ -282,6 +210,10 @@ public object MacPlatformServicesDefaultImpl : MacPlatformServices {
}
}
/**
* Executes [producer] inside a `NSAutoreleasePool` and returns its result, or `null` if the current OS is not macOS
* or if an exception is thrown (the exception is logged as a warning).
*/
@Suppress("TooGenericExceptionCaught")
public fun <T : Any> callMac(producer: () -> T?): T? {
if (!hostOs.isMacOS) return null
@@ -298,6 +230,7 @@ public object MacPlatformServicesDefaultImpl : MacPlatformServices {
}
}
/** [ProvidableCompositionLocal] that provides the active [MacPlatformServices] instance for the current window. */
@get:ApiStatus.Internal
@InternalJewelApi
public val LocalMacPlatformServices: ProvidableCompositionLocal<MacPlatformServices> = staticCompositionLocalOf {