Skip to content

Commit 79a85df

Browse files
Matthias Bläsingprrace
authored andcommitted
8353950: Clipboard interaction on Windows is unstable
8332271: Reading data from the clipboard from multiple threads crashes the JVM Reviewed-by: prr Backport-of: 92be782
1 parent 41928ae commit 79a85df

File tree

4 files changed

+150
-46
lines changed

4 files changed

+150
-46
lines changed

src/java.desktop/share/classes/sun/awt/datatransfer/SunClipboard.java

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -204,8 +204,9 @@ public Object getData(DataFlavor flavor)
204204
byte[] data = null;
205205
Transferable localeTransferable = null;
206206

207+
openClipboard(null);
208+
207209
try {
208-
openClipboard(null);
209210

210211
long[] formats = getClipboardFormats();
211212
Long lFormat = DataTransferer.getInstance().
@@ -318,12 +319,7 @@ protected void lostOwnershipNow(final AppContext disposedContext) {
318319
* @since 1.5
319320
*/
320321
protected long[] getClipboardFormatsOpenClose() {
321-
try {
322-
openClipboard(null);
323-
return getClipboardFormats();
324-
} finally {
325-
closeClipboard();
326-
}
322+
return getClipboardFormats();
327323
}
328324

329325
/**
@@ -356,15 +352,7 @@ public synchronized void addFlavorListener(FlavorListener listener) {
356352
flavorListeners.add(listener);
357353

358354
if (numberOfFlavorListeners++ == 0) {
359-
long[] currentFormats = null;
360-
try {
361-
openClipboard(null);
362-
currentFormats = getClipboardFormats();
363-
} catch (final IllegalStateException ignored) {
364-
} finally {
365-
closeClipboard();
366-
}
367-
this.currentFormats = currentFormats;
355+
this.currentFormats = getClipboardFormats();
368356

369357
registerClipboardViewerChecked();
370358
}

src/java.desktop/windows/classes/sun/awt/windows/WClipboard.java

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -29,7 +29,9 @@
2929
import java.awt.datatransfer.Transferable;
3030
import java.awt.datatransfer.UnsupportedFlavorException;
3131
import java.io.IOException;
32+
import java.lang.System.Logger.Level;
3233
import java.util.Map;
34+
import java.util.concurrent.locks.ReentrantLock;
3335

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

5254
private boolean isClipboardViewerRegistered;
5355

56+
private final ReentrantLock clipboardLocked = new ReentrantLock();
57+
5458
WClipboard() {
5559
super("System");
60+
// Register java side of the clipboard with the native side
61+
registerClipboard();
5662
}
5763

5864
@Override
@@ -104,18 +110,42 @@ protected void clearNativeContext() {}
104110

105111
/**
106112
* Call the Win32 OpenClipboard function. If newOwner is non-null,
107-
* we also call EmptyClipboard and take ownership.
113+
* we also call EmptyClipboard and take ownership. If this method call
114+
* succeeds, it must be followed by a call to {@link #closeClipboard()}.
108115
*
109116
* @throws IllegalStateException if the clipboard has not been opened
110117
*/
111118
@Override
112-
public native void openClipboard(SunClipboard newOwner) throws IllegalStateException;
119+
public void openClipboard(SunClipboard newOwner) throws IllegalStateException {
120+
if (!clipboardLocked.tryLock()) {
121+
throw new IllegalStateException("Failed to acquire clipboard lock");
122+
}
123+
try {
124+
openClipboard0(newOwner);
125+
} catch (IllegalStateException ex) {
126+
clipboardLocked.unlock();
127+
throw ex;
128+
}
129+
}
130+
113131
/**
114132
* Call the Win32 CloseClipboard function if we have clipboard ownership,
115133
* does nothing if we have not ownership.
116134
*/
117135
@Override
118-
public native void closeClipboard();
136+
public void closeClipboard() {
137+
if (clipboardLocked.isLocked()) {
138+
try {
139+
closeClipboard0();
140+
} finally {
141+
clipboardLocked.unlock();
142+
}
143+
}
144+
}
145+
146+
private native void openClipboard0(SunClipboard newOwner) throws IllegalStateException;
147+
private native void closeClipboard0();
148+
119149
/**
120150
* Call the Win32 SetClipboardData function.
121151
*/
@@ -157,16 +187,12 @@ private void handleContentsChanged() {
157187
return;
158188
}
159189

160-
long[] formats = null;
161190
try {
162-
openClipboard(null);
163-
formats = getClipboardFormats();
164-
} catch (IllegalStateException exc) {
165-
// do nothing to handle the exception, call checkChange(null)
166-
} finally {
167-
closeClipboard();
191+
long[] formats = getClipboardFormats();
192+
checkChange(formats);
193+
} catch (Throwable ex) {
194+
System.getLogger(WClipboard.class.getName()).log(Level.DEBUG, "Failed to process handleContentsChanged", ex);
168195
}
169-
checkChange(formats);
170196
}
171197

172198
/**
@@ -214,4 +240,6 @@ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorExcepti
214240
}
215241
};
216242
}
243+
244+
private native void registerClipboard();
217245
}

src/java.desktop/windows/native/libawt/windows/awt_Clipboard.cpp

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -69,9 +69,8 @@ void AwtClipboard::RegisterClipboardViewer(JNIEnv *env, jobject jclipboard) {
6969
return;
7070
}
7171

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

7675
jclass cls = env->GetObjectClass(jclipboard);
7776
AwtClipboard::handleContentsChangedMID =
@@ -128,11 +127,13 @@ Java_sun_awt_windows_WClipboard_init(JNIEnv *env, jclass cls)
128127
* Signature: (Lsun/awt/windows/WClipboard;)V
129128
*/
130129
JNIEXPORT void JNICALL
131-
Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self,
130+
Java_sun_awt_windows_WClipboard_openClipboard0(JNIEnv *env, jobject self,
132131
jobject newOwner)
133132
{
134133
TRY;
135134

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

138139
if (!::OpenClipboard(AwtToolkit::GetInstance().GetHWnd())) {
@@ -142,10 +143,6 @@ Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self,
142143
}
143144
if (newOwner != NULL) {
144145
AwtClipboard::GetOwnership();
145-
if (AwtClipboard::theCurrentClipboard != NULL) {
146-
env->DeleteGlobalRef(AwtClipboard::theCurrentClipboard);
147-
}
148-
AwtClipboard::theCurrentClipboard = env->NewGlobalRef(newOwner);
149146
}
150147

151148
CATCH_BAD_ALLOC;
@@ -157,7 +154,7 @@ Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self,
157154
* Signature: ()V
158155
*/
159156
JNIEXPORT void JNICALL
160-
Java_sun_awt_windows_WClipboard_closeClipboard(JNIEnv *env, jobject self)
157+
Java_sun_awt_windows_WClipboard_closeClipboard0(JNIEnv *env, jobject self)
161158
{
162159
TRY;
163160

@@ -297,23 +294,25 @@ Java_sun_awt_windows_WClipboard_getClipboardFormats
297294
{
298295
TRY;
299296

300-
DASSERT(::GetOpenClipboardWindow() == AwtToolkit::GetInstance().GetHWnd());
297+
unsigned int cFormats = 128; // Allocate enough space to hold all
298+
unsigned int pcFormatsOut = 0;
299+
unsigned int lpuiFormats[128] = { 0 };
301300

302-
jsize nFormats = ::CountClipboardFormats();
303-
jlongArray formats = env->NewLongArray(nFormats);
301+
VERIFY(::GetUpdatedClipboardFormats(lpuiFormats, 128, &pcFormatsOut));
302+
303+
jlongArray formats = env->NewLongArray(pcFormatsOut);
304304
if (formats == NULL) {
305305
throw std::bad_alloc();
306306
}
307-
if (nFormats == 0) {
307+
if (pcFormatsOut == 0) {
308308
return formats;
309309
}
310310
jboolean isCopy;
311311
jlong *lFormats = env->GetLongArrayElements(formats, &isCopy),
312312
*saveFormats = lFormats;
313-
UINT num = 0;
314313

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

319318
env->ReleaseLongArrayElements(formats, saveFormats, 0);
@@ -478,4 +477,16 @@ Java_sun_awt_windows_WClipboard_getClipboardData
478477
CATCH_BAD_ALLOC_RET(NULL);
479478
}
480479

480+
/*
481+
* Class: sun_awt_windows_WClipboard
482+
* Method: registerClipboard
483+
* Signature: ()V
484+
*/
485+
JNIEXPORT void JNICALL
486+
Java_sun_awt_windows_WClipboard_registerClipboard(JNIEnv *env, jobject self)
487+
{
488+
DASSERT(AwtClipboard::theCurrentClipboard == NULL);
489+
AwtClipboard::theCurrentClipboard = env->NewGlobalRef(self);
490+
}
491+
481492
} /* extern "C" */
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
@test
26+
@bug 8332271
27+
@summary tests that concurrent access to the clipboard does not crash the JVM
28+
@key headful
29+
@requires (os.family == "windows")
30+
@run main ConcurrentClipboardAccessTest
31+
*/
32+
import java.awt.Toolkit;
33+
import java.awt.datatransfer.Clipboard;
34+
import java.awt.datatransfer.DataFlavor;
35+
36+
public class ConcurrentClipboardAccessTest {
37+
38+
public static void main(String[] args) {
39+
Thread clipboardLoader1 = new Thread(new ClipboardLoader());
40+
clipboardLoader1.setDaemon(true);
41+
clipboardLoader1.start();
42+
Thread clipboardLoader2 = new Thread(new ClipboardLoader());
43+
clipboardLoader2.setDaemon(true);
44+
clipboardLoader2.start();
45+
long start = System.currentTimeMillis();
46+
while (true) {
47+
try {
48+
Thread.sleep(1000);
49+
} catch (InterruptedException ignored) {
50+
}
51+
long now = System.currentTimeMillis();
52+
if ((now - start) > (10L * 1000L)) {
53+
break;
54+
}
55+
}
56+
// Test is considered successful if the concurrent repeated reading
57+
// from clipboard succeeds for the allotted time and the JVM does not
58+
// crash.
59+
System.out.println("Shutdown normally");
60+
}
61+
62+
public static class ClipboardLoader implements Runnable {
63+
64+
@Override
65+
public void run() {
66+
final Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
67+
while (true) {
68+
try {
69+
if (systemClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
70+
systemClipboard.getData(DataFlavor.stringFlavor);
71+
}
72+
} catch (Exception ignored) {
73+
}
74+
}
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)