Skip to content

Commit 6c2372b

Browse files
HeikoKlarefedejeanne
authored andcommitted
[Win32] Fix potential memory leak in GC#drawImage #2499
When calling GC#drawImage(), an operation is created (like for every execution on GC) which stores the image to draw. Since the image may be disposed afterwards, a dispose listener is registered to the image, such that the image can be copied and maintained inside the operation in case of a disposal until that operation itself is disposed. The listener is currently not de-registered when disposing the GC and its operations, thus the GC operations (and thus also the containing GC) will still be referenced (as listeners from the image) until that image is disposed. Since images may be long living (or never be disposed at all), this can lead to a memory leak. This change removes the listener from the image upon disposal of a GC and its operations such that no memory can occur. Fixes #2499
1 parent c6a29a9 commit 6c2372b

File tree

3 files changed

+36
-1
lines changed

3 files changed

+36
-1
lines changed

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import java.util.*;
1818
import java.util.List;
19+
import java.util.function.*;
1920
import java.util.stream.*;
2021

2122
import org.eclipse.swt.*;
@@ -494,9 +495,11 @@ public void copyArea (Image image, int x, int y) {
494495
private abstract class ImageOperation extends Operation {
495496
private Image image;
496497

498+
private final Consumer<Image> disposeCallback = this::setCopyOfImage;
499+
497500
ImageOperation(Image image) {
498501
setImage(image);
499-
image.addOnDisposeListener(this::setCopyOfImage);
502+
image.addOnDisposeListener(disposeCallback);
500503
}
501504

502505
private void setImage(Image image) {
@@ -515,6 +518,11 @@ protected Image getImage() {
515518
return image;
516519
}
517520

521+
@Override
522+
void disposeAll() {
523+
image.removeOnDisposeListener(disposeCallback);
524+
super.disposeAll();
525+
}
518526
}
519527

520528
private class CopyAreaToImageOperation extends ImageOperation {

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,10 +1020,18 @@ void addOnDisposeListener(Consumer<Image> onDisposeListener) {
10201020
onDisposeListeners.add(onDisposeListener);
10211021
}
10221022

1023+
void removeOnDisposeListener(Consumer<Image> onDisposeListener) {
1024+
if (onDisposeListeners == null) {
1025+
return;
1026+
}
1027+
onDisposeListeners.remove(onDisposeListener);
1028+
}
1029+
10231030
@Override
10241031
public void dispose() {
10251032
if (onDisposeListeners != null) {
10261033
onDisposeListeners.forEach(listener -> listener.accept(this));
1034+
onDisposeListeners.clear();
10271035
}
10281036
super.dispose();
10291037
}

tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import static org.junit.jupiter.api.Assumptions.assumeFalse;
3030
import static org.junit.jupiter.api.Assumptions.assumeTrue;
3131

32+
import java.lang.ref.WeakReference;
3233
import java.util.concurrent.atomic.AtomicReference;
3334

3435
import org.eclipse.swt.SWT;
@@ -804,6 +805,24 @@ public void test_bug1288_createGCFromImageFromNonDisplayThread() throws Interrup
804805
assertNull(exceptionReference.get(), "Creating a GC from an Image without a device threw an exception");
805806
}
806807

808+
// Tests that a GC instance is cleaned from memory after it was disposed.
809+
// Primarily supposed to test that the operations (currently only implemented for Windows) do not produce any leaks.
810+
@Test
811+
public void test_noMemoryLeakAfterDispose() {
812+
GC testGC = new GC(display);
813+
Image image = new Image(display, 1, 1);
814+
testGC.setFont(display.getSystemFont());
815+
testGC.setClipping(new Rectangle(0, 0, 2, 2));
816+
testGC.drawImage(image, 0, 0);
817+
testGC.drawText("Test", 0, 0);
818+
testGC.drawLine(0, 0, 5, 5);
819+
WeakReference<GC> testGCReference = new WeakReference<>(testGC);
820+
testGC.dispose();
821+
testGC = null;
822+
System.gc();
823+
assertNull(testGCReference.get());
824+
}
825+
807826
/* custom */
808827
Display display;
809828
Shell shell;

0 commit comments

Comments
 (0)