Skip to content

8353950: Clipboard interaction on Windows is unstable #25897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
Original file line number Diff line number Diff line change
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -204,8 +204,9 @@ public Object getData(DataFlavor flavor)
byte[] data = null;
Transferable localeTransferable = null;

openClipboard(null);

try {
openClipboard(null);

long[] formats = getClipboardFormats();
Long lFormat = DataTransferer.getInstance().
Expand Down Expand Up @@ -318,12 +319,7 @@ protected void lostOwnershipNow(final AppContext disposedContext) {
* @since 1.5
*/
protected long[] getClipboardFormatsOpenClose() {
try {
openClipboard(null);
return getClipboardFormats();
} finally {
closeClipboard();
}
return getClipboardFormats();
}

/**
Expand Down Expand Up @@ -356,15 +352,7 @@ public synchronized void addFlavorListener(FlavorListener listener) {
flavorListeners.add(listener);

if (numberOfFlavorListeners++ == 0) {
long[] currentFormats = null;
try {
openClipboard(null);
currentFormats = getClipboardFormats();
} catch (final IllegalStateException ignored) {
} finally {
closeClipboard();
}
this.currentFormats = currentFormats;
this.currentFormats = getClipboardFormats();

registerClipboardViewerChecked();
}
Expand Down
52 changes: 40 additions & 12 deletions src/java.desktop/windows/classes/sun/awt/windows/WClipboard.java
Original file line number Diff line number Diff line change
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -29,7 +29,9 @@
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.lang.System.Logger.Level;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

import sun.awt.datatransfer.DataTransferer;
import sun.awt.datatransfer.SunClipboard;
Expand All @@ -51,8 +53,12 @@ final class WClipboard extends SunClipboard {

private boolean isClipboardViewerRegistered;

private final ReentrantLock clipboardLocked = new ReentrantLock();

WClipboard() {
super("System");
// Register java side of the clipboard with the native side
registerClipboard();
}

@Override
Expand Down Expand Up @@ -104,18 +110,42 @@ protected void clearNativeContext() {}

/**
* 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
*/
@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,
* does nothing if we have not ownership.
*/
@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.
*/
Expand Down Expand Up @@ -157,16 +187,12 @@ private void handleContentsChanged() {
return;
}

long[] formats = null;
try {
openClipboard(null);
formats = getClipboardFormats();
} catch (IllegalStateException exc) {
// do nothing to handle the exception, call checkChange(null)
} finally {
closeClipboard();
long[] formats = getClipboardFormats();
checkChange(formats);
} catch (Throwable ex) {
System.getLogger(WClipboard.class.getName()).log(Level.DEBUG, "Failed to process handleContentsChanged", ex);
}
checkChange(formats);
}

/**
Expand Down Expand Up @@ -214,4 +240,6 @@ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorExcepti
}
};
}

private native void registerClipboard();
}
45 changes: 28 additions & 17 deletions src/java.desktop/windows/native/libawt/windows/awt_Clipboard.cpp
Original file line number Diff line number Diff line change
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -69,9 +69,8 @@ void AwtClipboard::RegisterClipboardViewer(JNIEnv *env, jobject jclipboard) {
return;
}

if (theCurrentClipboard == NULL) {
theCurrentClipboard = env->NewGlobalRef(jclipboard);
}
DASSERT(AwtClipboard::theCurrentClipboard != NULL);
DASSERT(env->IsSameObject(AwtClipboard::theCurrentClipboard, jclipboard));

jclass cls = env->GetObjectClass(jclipboard);
AwtClipboard::handleContentsChangedMID =
Expand Down Expand Up @@ -128,11 +127,13 @@ Java_sun_awt_windows_WClipboard_init(JNIEnv *env, jclass cls)
* Signature: (Lsun/awt/windows/WClipboard;)V
*/
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)
{
TRY;

DASSERT(AwtClipboard::theCurrentClipboard != NULL);
DASSERT(newOwner == NULL || env->IsSameObject(AwtClipboard::theCurrentClipboard, newOwner));
DASSERT(::GetOpenClipboardWindow() != AwtToolkit::GetInstance().GetHWnd());

if (!::OpenClipboard(AwtToolkit::GetInstance().GetHWnd())) {
Expand All @@ -142,10 +143,6 @@ Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self,
}
if (newOwner != NULL) {
AwtClipboard::GetOwnership();
if (AwtClipboard::theCurrentClipboard != NULL) {
env->DeleteGlobalRef(AwtClipboard::theCurrentClipboard);
}
AwtClipboard::theCurrentClipboard = env->NewGlobalRef(newOwner);
}

CATCH_BAD_ALLOC;
Expand All @@ -157,7 +154,7 @@ Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self,
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_sun_awt_windows_WClipboard_closeClipboard(JNIEnv *env, jobject self)
Java_sun_awt_windows_WClipboard_closeClipboard0(JNIEnv *env, jobject self)
{
TRY;

Expand Down Expand Up @@ -297,23 +294,25 @@ Java_sun_awt_windows_WClipboard_getClipboardFormats
{
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();
jlongArray formats = env->NewLongArray(nFormats);
VERIFY(::GetUpdatedClipboardFormats(lpuiFormats, 128, &pcFormatsOut));

jlongArray formats = env->NewLongArray(pcFormatsOut);
if (formats == NULL) {
throw std::bad_alloc();
}
if (nFormats == 0) {
if (pcFormatsOut == 0) {
return formats;
}
jboolean isCopy;
jlong *lFormats = env->GetLongArrayElements(formats, &isCopy),
*saveFormats = lFormats;
UINT num = 0;

for (jsize i = 0; i < nFormats; i++, lFormats++) {
*lFormats = num = ::EnumClipboardFormats(num);
for (unsigned int i = 0; i < pcFormatsOut; i++, lFormats++) {
*lFormats = lpuiFormats[i];
}

env->ReleaseLongArrayElements(formats, saveFormats, 0);
Expand Down Expand Up @@ -478,4 +477,16 @@ Java_sun_awt_windows_WClipboard_getClipboardData
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" */
77 changes: 77 additions & 0 deletions test/jdk/java/awt/Clipboard/ConcurrentClipboardAccessTest.java
Original file line number Diff line number Diff line change
@@ -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) {
}
}
}
}
}