diff options
| -rw-r--r-- | api/current.txt | 7 | ||||
| -rw-r--r-- | core/java/android/util/CloseGuard.java | 138 | ||||
| -rw-r--r-- | core/tests/coretests/Android.bp | 1 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/util/CloseGuardTest.java | 98 |
4 files changed, 244 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt index e741629679b9..02bef936aeba 100644 --- a/api/current.txt +++ b/api/current.txt @@ -48246,6 +48246,13 @@ package android.util { ctor public Base64OutputStream(java.io.OutputStream, int); } + public final class CloseGuard { + ctor public CloseGuard(); + method public void close(); + method public void open(@NonNull String); + method public void warnIfOpen(); + } + @Deprecated public final class Config { field @Deprecated public static final boolean DEBUG = false; field @Deprecated public static final boolean LOGD = true; diff --git a/core/java/android/util/CloseGuard.java b/core/java/android/util/CloseGuard.java new file mode 100644 index 000000000000..c39a6c9aac93 --- /dev/null +++ b/core/java/android/util/CloseGuard.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import android.annotation.NonNull; + +/** + * CloseGuard is a mechanism for flagging implicit finalizer cleanup of + * resources that should have been cleaned up by explicit close + * methods (aka "explicit termination methods" in Effective Java). + * <p> + * A simple example: <pre> {@code + * class Foo { + * + * private final CloseGuard guard = CloseGuard.get(); + * + * ... + * + * public Foo() { + * ...; + * guard.open("cleanup"); + * } + * + * public void cleanup() { + * guard.close(); + * ...; + * } + * + * protected void finalize() throws Throwable { + * try { + * // Note that guard could be null if the constructor threw. + * if (guard != null) { + * guard.warnIfOpen(); + * } + * cleanup(); + * } finally { + * super.finalize(); + * } + * } + * } + * }</pre> + * + * In usage where the resource to be explicitly cleaned up is + * allocated after object construction, CloseGuard protection can + * be deferred. For example: <pre> {@code + * class Bar { + * + * private final CloseGuard guard = CloseGuard.get(); + * + * ... + * + * public Bar() { + * ...; + * } + * + * public void connect() { + * ...; + * guard.open("cleanup"); + * } + * + * public void cleanup() { + * guard.close(); + * ...; + * Reference.reachabilityFence(this); + * // For full correctness in the absence of a close() call, other methods may also need + * // reachabilityFence() calls. + * } + * + * protected void finalize() throws Throwable { + * try { + * // Note that guard could be null if the constructor threw. + * if (guard != null) { + * guard.warnIfOpen(); + * } + * cleanup(); + * } finally { + * super.finalize(); + * } + * } + * } + * }</pre> + * + * When used in a constructor, calls to {@code open} should occur at + * the end of the constructor since an exception that would cause + * abrupt termination of the constructor will mean that the user will + * not have a reference to the object to cleanup explicitly. When used + * in a method, the call to {@code open} should occur just after + * resource acquisition. + */ +public final class CloseGuard { + private final dalvik.system.CloseGuard mImpl; + + /** + * Constructs a new CloseGuard instance. + * {@link #open(String)} can be used to set up the instance to warn on failure to close. + */ + public CloseGuard() { + mImpl = dalvik.system.CloseGuard.get(); + } + + /** + * Initializes the instance with a warning that the caller should have explicitly called the + * {@code closeMethodName} method instead of relying on finalization. + * + * @param closeMethodName non-null name of explicit termination method. Printed by warnIfOpen. + * @throws NullPointerException if closeMethodName is null. + */ + public void open(@NonNull String closeMethodName) { + mImpl.open(closeMethodName); + } + + /** Marks this CloseGuard instance as closed to avoid warnings on finalization. */ + public void close() { + mImpl.close(); + } + + /** + * Logs a warning if the caller did not properly cleanup by calling an explicit close method + * before finalization. + */ + public void warnIfOpen() { + mImpl.warnIfOpen(); + } +} diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 1670d49a46c4..4d4f4476a974 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -24,6 +24,7 @@ android_test { ], static_libs: [ "frameworks-base-testutils", + "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport "core-tests-support", "android-common", "frameworks-core-util-lib", diff --git a/core/tests/coretests/src/android/util/CloseGuardTest.java b/core/tests/coretests/src/android/util/CloseGuardTest.java new file mode 100644 index 000000000000..d86c7b79fad6 --- /dev/null +++ b/core/tests/coretests/src/android/util/CloseGuardTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import libcore.dalvik.system.CloseGuardSupport; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +/** Unit tests for {@link android.util.CloseGuard} */ +public class CloseGuardTest { + + @Rule + public final TestRule rule = CloseGuardSupport.getRule(); + + @Test + public void testEnabled_NotOpen() throws Throwable { + ResourceOwner owner = new ResourceOwner(); + assertUnreleasedResources(owner, 0); + } + + @Test + public void testEnabled_OpenNotClosed() throws Throwable { + ResourceOwner owner = new ResourceOwner(); + owner.open(); + assertUnreleasedResources(owner, 1); + } + + @Test + public void testEnabled_OpenThenClosed() throws Throwable { + ResourceOwner owner = new ResourceOwner(); + owner.open(); + owner.close(); + assertUnreleasedResources(owner, 0); + } + + @Test(expected = NullPointerException.class) + public void testOpen_withNullMethodName_throwsNPE() throws Throwable { + CloseGuard closeGuard = new CloseGuard(); + closeGuard.open(null); + } + + private void assertUnreleasedResources(ResourceOwner owner, int expectedCount) + throws Throwable { + try { + CloseGuardSupport.getFinalizerChecker().accept(owner, expectedCount); + } finally { + // Close the resource so that CloseGuard does not generate a warning for real when it + // is actually finalized. + owner.close(); + } + } + + /** + * A test user of {@link CloseGuard}. + */ + private static class ResourceOwner { + + private final CloseGuard mCloseGuard; + + ResourceOwner() { + mCloseGuard = new CloseGuard(); + } + + public void open() { + mCloseGuard.open("close"); + } + + public void close() { + mCloseGuard.close(); + } + + /** + * Make finalize public so that it can be tested directly without relying on garbage + * collection to trigger it. + */ + @Override + public void finalize() throws Throwable { + mCloseGuard.warnIfOpen(); + super.finalize(); + } + } +} |