Bug 569752 - Detect non disposed OS resources via Object.finalize()
New "org.eclipse.swt.graphics.Resource.reportNonDisposed" system
property is added that can be used to track not disposed resources:
- if set to "true", SWT will report not disposed SWT resource without
allocating exception at SWT resource creation time.
- if set to "stacks", SWT will aditionally report the stack recorded at
resource creation time.
A custom NonDisposedReporter can be added to customize how not disposed
SWT resources should be reported.
Note: Fonts created from native handle are ignored because they don't
own the handle and can't be disposed. Same for Region on macOS.
Change-Id: I3bfdc9d5b41eba2cac5f187ea1fe771f9ff36750
Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
diff --git a/bundles/org.eclipse.swt.browser.chromium/META-INF/MANIFEST.MF b/bundles/org.eclipse.swt.browser.chromium/META-INF/MANIFEST.MF
index 8567c89..655c1ff 100644
--- a/bundles/org.eclipse.swt.browser.chromium/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.swt.browser.chromium/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
Bundle-Name: Chromium SWT Widget
Bundle-Vendor: Eclipse.org
Bundle-SymbolicName: org.eclipse.swt.browser.chromium;singleton:=true
-Bundle-Version: 3.115.200.qualifier
+Bundle-Version: 3.116.0.qualifier
Bundle-ManifestVersion: 2
Eclipse-PlatformFilter: (osgi.arch=x86_64)
SWT-Arch: x86_64
diff --git a/bundles/org.eclipse.swt.browser.chromium/pom.xml b/bundles/org.eclipse.swt.browser.chromium/pom.xml
index ce3f6af..eef7ee1 100644
--- a/bundles/org.eclipse.swt.browser.chromium/pom.xml
+++ b/bundles/org.eclipse.swt.browser.chromium/pom.xml
@@ -21,7 +21,7 @@
</parent>
<groupId>org.eclipse.swt</groupId>
<artifactId>org.eclipse.swt.browser.chromium</artifactId>
- <version>3.115.200-SNAPSHOT</version>
+ <version>3.116.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
<properties>
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Font.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Font.java
index a62cf05..a3c20ed 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Font.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Font.java
@@ -292,6 +292,12 @@
public static Font cocoa_new(Device device, NSFont handle) {
Font font = new Font(device);
font.handle = handle;
+ /*
+ * When created this way, Font doesn't own its .handle, and
+ * for this reason it can't be disposed. Tell leak detector
+ * to just ignore it.
+ */
+ font.nonDisposedIgnore = true;
return font;
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Region.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Region.java
index 304368e..adfbcbe 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Region.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Region.java
@@ -97,6 +97,12 @@
Region(Device device, long handle) {
super(device);
this.handle = handle;
+ /*
+ * When created this way, Font doesn't own its .handle, and
+ * for this reason it can't be disposed. Tell leak detector
+ * to just ignore it.
+ */
+ this.nonDisposedIgnore = true;
}
/**
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/Resource.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/Resource.java
index 625b416..ffce5e0 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/Resource.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/Resource.java
@@ -41,10 +41,71 @@
public abstract class Resource {
/**
+ * Stack recorded at resource allocation time, wraped as Exception for
+ * convenience
+ *
+ * @since 3.116
+ */
+ public static class NonDisposedException extends Exception {
+ static final long serialVersionUID = 1;
+ }
+
+ /**
+ * Reports not disposed SWT resource
+ *
+ * @since 3.116
+ */
+ public interface NonDisposedReporter {
+ /**
+ * Note that this is called as part of {@link Object#finalize()}, please
+ * read its docs.
+ *
+ * @param resource the Resource which wasn't properly disposed
+ * @param allocationStack stack trace that allocated the Resource, or
+ * null if {@link #collectAllocationStacks} is
+ * false.
+ * @see Resource#trackNonDisposed(boolean, NonDisposedReporter)
+ */
+ void onNonDisposedResource(Resource resource, NonDisposedException allocationStack);
+ }
+
+ /**
* the device where this resource was created
*/
Device device;
+ /**
+ * Used to report not disposed SWT resources, null by default
+ */
+ private static NonDisposedReporter nonDisposedReporter;
+
+ /**
+ * Set to {@code true} if leak detector should ignore not disposed resource
+ */
+ boolean nonDisposedIgnore;
+
+ /**
+ * Set to {@code true} if leak detector should create exceptions for not disposed resources
+ */
+ private static boolean collectAllocationStacks;
+
+ /**
+ * Recorded at object creation if {@link #trackNonDisposed(boolean)} was
+ * called with {@code true}, used to track resource disposal
+ */
+ private NonDisposedException allocationStack;
+
+ static {
+ final String property = System.getProperty("org.eclipse.swt.graphics.Resource.reportNonDisposed");
+ if (property != null) {
+ if (property.equalsIgnoreCase("stacks")) {
+ trackNonDisposed(true);
+ } else if (property.equalsIgnoreCase("true")) {
+ trackNonDisposed(false);
+ }
+ }
+ }
+
public Resource() {
}
@@ -52,6 +113,9 @@
if (device == null) device = Device.getDevice();
if (device == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
this.device = device;
+ if (collectAllocationStacks) {
+ allocationStack = new NonDisposedException();
+ }
}
void destroy() {
@@ -72,6 +136,17 @@
}
/**
+ * Returns the allocation stack if {@link #collectAllocationStacks} is enabled.
+ * Otherwise, will return {@code null}.
+ *
+ * @see #trackNonDisposed(boolean)
+ * @since 3.116
+ */
+public NonDisposedException getAllocationStack() {
+ return allocationStack;
+}
+
+/**
* Returns the <code>Device</code> where this resource was
* created.
*
@@ -101,4 +176,57 @@
*/
public abstract boolean isDisposed();
+@Override
+protected void finalize() {
+ if (nonDisposedIgnore) return;
+ if (nonDisposedReporter == null) return;
+ if (isDisposed()) return;
+
+ // Color doesn't really have any resource to be leaked, ignore
+ if (this instanceof Color) return;
+
+ nonDisposedReporter.onNonDisposedResource(this, allocationStack);
+}
+
+/**
+ * Enables detection of Resource objects for which {@link #dispose()} wasn't
+ * called, which means a leak of native memory and/or OS resources.
+ *
+ * @param collectAllocationStacks whether to collect allocation stacks for
+ * better reports. This is a performance
+ * tradeoff. Changing this value will only affect
+ * future objects.
+ * @param reporter object used to report detected errors. Use
+ * null to disable tracking. Setting a new
+ * reporter has an immediate effect.
+ *
+ * @see #getAllocationStack()
+ * @since 3.116
+ */
+public static void trackNonDisposed(boolean collectAllocationStacks, NonDisposedReporter reporter) {
+ nonDisposedReporter = reporter;
+ Resource.collectAllocationStacks = collectAllocationStacks;
+}
+
+/**
+ * Enables detection of Resource objects for which {@link #dispose()} wasn't
+ * called, which means a leak of native memory and/or OS resources. Uses default
+ * reporter that will print to {@link System#err}.
+ *
+ * @param collectAllocationStacks whether to collect allocation stacks for
+ * better reports. This is a performance
+ * tradeoff. Changing this value will only affect
+ * future objects.
+ *
+ * @see #getAllocationStack()
+ * @since 3.116
+ */
+public static void trackNonDisposed(boolean collectAllocationStacks) {
+ trackNonDisposed(collectAllocationStacks, (object, exception) -> {
+ System.err.println("Resource was not properly disposed: " + object);
+ if (exception != null)
+ exception.printStackTrace();
+ });
+}
+
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Font.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Font.java
index 21e63f9..c18dea0 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Font.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Font.java
@@ -233,6 +233,12 @@
public static Font gtk_new(Device device, long handle) {
Font font = new Font(device);
font.handle = handle;
+ /*
+ * When created this way, Font doesn't own its .handle, and
+ * for this reason it can't be disposed. Tell leak detector
+ * to just ignore it.
+ */
+ font.nonDisposedIgnore = true;
return font;
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java
index ee6e908..bdaf885 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java
@@ -269,6 +269,12 @@
public static Font win32_new(Device device, long handle) {
Font font = new Font(device);
font.handle = handle;
+ /*
+ * When created this way, Font doesn't own its .handle, and
+ * for this reason it can't be disposed. Tell leak detector
+ * to just ignore it.
+ */
+ font.nonDisposedIgnore = true;
return font;
}
diff --git a/bundles/org.eclipse.swt/META-INF/MANIFEST.MF b/bundles/org.eclipse.swt/META-INF/MANIFEST.MF
index 579190b..a8394e3 100644
--- a/bundles/org.eclipse.swt/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.swt/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
Bundle-Name: %pluginName
Bundle-Vendor: %providerName
Bundle-SymbolicName: org.eclipse.swt; singleton:=true
-Bundle-Version: 3.115.200.qualifier
+Bundle-Version: 3.116.0.qualifier
Bundle-ManifestVersion: 2
Bundle-Localization: plugin
DynamicImport-Package: org.eclipse.swt.accessibility2
diff --git a/bundles/org.eclipse.swt/build.xml b/bundles/org.eclipse.swt/build.xml
index 2f44206..22fd01c 100644
--- a/bundles/org.eclipse.swt/build.xml
+++ b/bundles/org.eclipse.swt/build.xml
@@ -18,7 +18,7 @@
<target name="init">
<property name="plugin" value="org.eclipse.swt" />
- <property name="version.suffix" value="3.115.200" />
+ <property name="version.suffix" value="3.116.0" />
<property name="full.name" value="${plugin}_${version.suffix}" />
<property name="temp.folder" value="${basedir}/temp.folder" />
<property name="plugin.destination" value="${basedir}" />
diff --git a/bundles/org.eclipse.swt/pom.xml b/bundles/org.eclipse.swt/pom.xml
index c76244c..c92c177 100644
--- a/bundles/org.eclipse.swt/pom.xml
+++ b/bundles/org.eclipse.swt/pom.xml
@@ -19,7 +19,7 @@
</parent>
<groupId>org.eclipse.swt</groupId>
<artifactId>org.eclipse.swt</artifactId>
- <version>3.115.200-SNAPSHOT</version>
+ <version>3.116.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
<properties>
diff --git a/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Bug569752_DetectNonDisposedOsResources.java b/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Bug569752_DetectNonDisposedOsResources.java
new file mode 100644
index 0000000..4f45913
--- /dev/null
+++ b/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Bug569752_DetectNonDisposedOsResources.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Syntevo and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Syntevo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.tests.manual;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Resource;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.*;
+
+public class Bug569752_DetectNonDisposedOsResources {
+ public static void main(String[] args) {
+ // System.setProperty("org.eclipse.swt.graphics.Resource.reportNonDisposed", "stacks");
+ // System.setProperty("org.eclipse.swt.graphics.Resource.reportNonDisposed", "false");
+
+ final Display display = new Display();
+ final Shell shell = new Shell(display);
+ shell.setLayout(new GridLayout(1, true));
+
+ Label hint = new Label(shell, 0);
+ hint.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ hint.setText(
+ "1) Click the button below\n" +
+ "2) With patch, there's a leak report for Image"
+ );
+
+ Button chkDispose = new Button(shell, SWT.CHECK);
+ chkDispose.setText("Dispose objects");
+
+ Button btnTest = new Button(shell, SWT.PUSH);
+ btnTest.setText("Leak Image");
+ btnTest.addListener(SWT.Selection, event -> {
+ Image image = new Image(display, 10, 10);
+
+ if (chkDispose.getSelection())
+ image.dispose();
+
+ image = null;
+ System.gc();
+ });
+
+ Resource.trackNonDisposed(true, (object, exception) -> {
+ System.err.println("Snippet reporter: Resource was not properly disposed: " + object);
+ if (exception != null)
+ exception.printStackTrace();
+ });
+
+ shell.pack();
+ shell.open();
+
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+
+ display.dispose();
+ }
+}
\ No newline at end of file