8353950: Clipboard interaction on Windows is unstable

8332271: Reading data from the clipboard from multiple threads crashes the JVM

Reviewed-by: abhiscxk, dnguyen
This commit is contained in:
Matthias Bläsing 2025-06-10 00:21:18 +00:00 committed by SendaoYan
parent bcf860703d
commit 92be7821f5
4 changed files with 150 additions and 46 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -204,8 +204,9 @@ public abstract class SunClipboard extends Clipboard
byte[] data = null; byte[] data = null;
Transferable localeTransferable = null; Transferable localeTransferable = null;
openClipboard(null);
try { try {
openClipboard(null);
long[] formats = getClipboardFormats(); long[] formats = getClipboardFormats();
Long lFormat = DataTransferer.getInstance(). Long lFormat = DataTransferer.getInstance().
@ -318,12 +319,7 @@ public abstract class SunClipboard extends Clipboard
* @since 1.5 * @since 1.5
*/ */
protected long[] getClipboardFormatsOpenClose() { protected long[] getClipboardFormatsOpenClose() {
try { return getClipboardFormats();
openClipboard(null);
return getClipboardFormats();
} finally {
closeClipboard();
}
} }
/** /**
@ -356,15 +352,7 @@ public abstract class SunClipboard extends Clipboard
flavorListeners.add(listener); flavorListeners.add(listener);
if (numberOfFlavorListeners++ == 0) { if (numberOfFlavorListeners++ == 0) {
long[] currentFormats = null; this.currentFormats = getClipboardFormats();
try {
openClipboard(null);
currentFormats = getClipboardFormats();
} catch (final IllegalStateException ignored) {
} finally {
closeClipboard();
}
this.currentFormats = currentFormats;
registerClipboardViewerChecked(); registerClipboardViewerChecked();
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -29,7 +29,9 @@ import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable; import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException; import java.io.IOException;
import java.lang.System.Logger.Level;
import java.util.Map; import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import sun.awt.datatransfer.DataTransferer; import sun.awt.datatransfer.DataTransferer;
import sun.awt.datatransfer.SunClipboard; import sun.awt.datatransfer.SunClipboard;
@ -51,8 +53,12 @@ final class WClipboard extends SunClipboard {
private boolean isClipboardViewerRegistered; private boolean isClipboardViewerRegistered;
private final ReentrantLock clipboardLocked = new ReentrantLock();
WClipboard() { WClipboard() {
super("System"); super("System");
// Register java side of the clipboard with the native side
registerClipboard();
} }
@Override @Override
@ -104,18 +110,42 @@ final class WClipboard extends SunClipboard {
/** /**
* Call the Win32 OpenClipboard function. If newOwner is non-null, * Call the Win32 OpenClipboard function. If newOwner is non-null,
* we also call EmptyClipboard and take ownership. * we also call EmptyClipboard and take ownership. If this method call
* succeeds, it must be followed by a call to {@link #closeClipboard()}.
* *
* @throws IllegalStateException if the clipboard has not been opened * @throws IllegalStateException if the clipboard has not been opened
*/ */
@Override @Override
public native void openClipboard(SunClipboard newOwner) throws IllegalStateException; public void openClipboard(SunClipboard newOwner) throws IllegalStateException {
if (!clipboardLocked.tryLock()) {
throw new IllegalStateException("Failed to acquire clipboard lock");
}
try {
openClipboard0(newOwner);
} catch (IllegalStateException ex) {
clipboardLocked.unlock();
throw ex;
}
}
/** /**
* Call the Win32 CloseClipboard function if we have clipboard ownership, * Call the Win32 CloseClipboard function if we have clipboard ownership,
* does nothing if we have not ownership. * does nothing if we have not ownership.
*/ */
@Override @Override
public native void closeClipboard(); public void closeClipboard() {
if (clipboardLocked.isLocked()) {
try {
closeClipboard0();
} finally {
clipboardLocked.unlock();
}
}
}
private native void openClipboard0(SunClipboard newOwner) throws IllegalStateException;
private native void closeClipboard0();
/** /**
* Call the Win32 SetClipboardData function. * Call the Win32 SetClipboardData function.
*/ */
@ -157,16 +187,12 @@ final class WClipboard extends SunClipboard {
return; return;
} }
long[] formats = null;
try { try {
openClipboard(null); long[] formats = getClipboardFormats();
formats = getClipboardFormats(); checkChange(formats);
} catch (IllegalStateException exc) { } catch (Throwable ex) {
// do nothing to handle the exception, call checkChange(null) System.getLogger(WClipboard.class.getName()).log(Level.DEBUG, "Failed to process handleContentsChanged", ex);
} finally {
closeClipboard();
} }
checkChange(formats);
} }
/** /**
@ -214,4 +240,6 @@ final class WClipboard extends SunClipboard {
} }
}; };
} }
private native void registerClipboard();
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -69,9 +69,8 @@ void AwtClipboard::RegisterClipboardViewer(JNIEnv *env, jobject jclipboard) {
return; return;
} }
if (theCurrentClipboard == NULL) { DASSERT(AwtClipboard::theCurrentClipboard != NULL);
theCurrentClipboard = env->NewGlobalRef(jclipboard); DASSERT(env->IsSameObject(AwtClipboard::theCurrentClipboard, jclipboard));
}
jclass cls = env->GetObjectClass(jclipboard); jclass cls = env->GetObjectClass(jclipboard);
AwtClipboard::handleContentsChangedMID = AwtClipboard::handleContentsChangedMID =
@ -128,11 +127,13 @@ Java_sun_awt_windows_WClipboard_init(JNIEnv *env, jclass cls)
* Signature: (Lsun/awt/windows/WClipboard;)V * Signature: (Lsun/awt/windows/WClipboard;)V
*/ */
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self, Java_sun_awt_windows_WClipboard_openClipboard0(JNIEnv *env, jobject self,
jobject newOwner) jobject newOwner)
{ {
TRY; TRY;
DASSERT(AwtClipboard::theCurrentClipboard != NULL);
DASSERT(newOwner == NULL || env->IsSameObject(AwtClipboard::theCurrentClipboard, newOwner));
DASSERT(::GetOpenClipboardWindow() != AwtToolkit::GetInstance().GetHWnd()); DASSERT(::GetOpenClipboardWindow() != AwtToolkit::GetInstance().GetHWnd());
if (!::OpenClipboard(AwtToolkit::GetInstance().GetHWnd())) { if (!::OpenClipboard(AwtToolkit::GetInstance().GetHWnd())) {
@ -142,10 +143,6 @@ Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self,
} }
if (newOwner != NULL) { if (newOwner != NULL) {
AwtClipboard::GetOwnership(); AwtClipboard::GetOwnership();
if (AwtClipboard::theCurrentClipboard != NULL) {
env->DeleteGlobalRef(AwtClipboard::theCurrentClipboard);
}
AwtClipboard::theCurrentClipboard = env->NewGlobalRef(newOwner);
} }
CATCH_BAD_ALLOC; CATCH_BAD_ALLOC;
@ -157,7 +154,7 @@ Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self,
* Signature: ()V * Signature: ()V
*/ */
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_sun_awt_windows_WClipboard_closeClipboard(JNIEnv *env, jobject self) Java_sun_awt_windows_WClipboard_closeClipboard0(JNIEnv *env, jobject self)
{ {
TRY; TRY;
@ -297,23 +294,25 @@ Java_sun_awt_windows_WClipboard_getClipboardFormats
{ {
TRY; TRY;
DASSERT(::GetOpenClipboardWindow() == AwtToolkit::GetInstance().GetHWnd()); unsigned int cFormats = 128; // Allocate enough space to hold all
unsigned int pcFormatsOut = 0;
unsigned int lpuiFormats[128] = { 0 };
jsize nFormats = ::CountClipboardFormats(); VERIFY(::GetUpdatedClipboardFormats(lpuiFormats, 128, &pcFormatsOut));
jlongArray formats = env->NewLongArray(nFormats);
jlongArray formats = env->NewLongArray(pcFormatsOut);
if (formats == NULL) { if (formats == NULL) {
throw std::bad_alloc(); throw std::bad_alloc();
} }
if (nFormats == 0) { if (pcFormatsOut == 0) {
return formats; return formats;
} }
jboolean isCopy; jboolean isCopy;
jlong *lFormats = env->GetLongArrayElements(formats, &isCopy), jlong *lFormats = env->GetLongArrayElements(formats, &isCopy),
*saveFormats = lFormats; *saveFormats = lFormats;
UINT num = 0;
for (jsize i = 0; i < nFormats; i++, lFormats++) { for (unsigned int i = 0; i < pcFormatsOut; i++, lFormats++) {
*lFormats = num = ::EnumClipboardFormats(num); *lFormats = lpuiFormats[i];
} }
env->ReleaseLongArrayElements(formats, saveFormats, 0); env->ReleaseLongArrayElements(formats, saveFormats, 0);
@ -478,4 +477,16 @@ Java_sun_awt_windows_WClipboard_getClipboardData
CATCH_BAD_ALLOC_RET(NULL); CATCH_BAD_ALLOC_RET(NULL);
} }
/*
* Class: sun_awt_windows_WClipboard
* Method: registerClipboard
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_sun_awt_windows_WClipboard_registerClipboard(JNIEnv *env, jobject self)
{
DASSERT(AwtClipboard::theCurrentClipboard == NULL);
AwtClipboard::theCurrentClipboard = env->NewGlobalRef(self);
}
} /* extern "C" */ } /* extern "C" */

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
@test
@bug 8332271
@summary tests that concurrent access to the clipboard does not crash the JVM
@key headful
@requires (os.family == "windows")
@run main ConcurrentClipboardAccessTest
*/
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
public class ConcurrentClipboardAccessTest {
public static void main(String[] args) {
Thread clipboardLoader1 = new Thread(new ClipboardLoader());
clipboardLoader1.setDaemon(true);
clipboardLoader1.start();
Thread clipboardLoader2 = new Thread(new ClipboardLoader());
clipboardLoader2.setDaemon(true);
clipboardLoader2.start();
long start = System.currentTimeMillis();
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
long now = System.currentTimeMillis();
if ((now - start) > (10L * 1000L)) {
break;
}
}
// Test is considered successful if the concurrent repeated reading
// from clipboard succeeds for the allotted time and the JVM does not
// crash.
System.out.println("Shutdown normally");
}
public static class ClipboardLoader implements Runnable {
@Override
public void run() {
final Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
while (true) {
try {
if (systemClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
systemClipboard.getData(DataFlavor.stringFlavor);
}
} catch (Exception ignored) {
}
}
}
}
}