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:
parent
bcf860703d
commit
92be7821f5
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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" */
|
||||||
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user