1fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin/*
2fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * Copyright (C) 2016 The Android Open Source Project
3fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *
4fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * Licensed under the Apache License, Version 2.0 (the "License");
5fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * you may not use this file except in compliance with the License.
6fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * You may obtain a copy of the License at
7fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *
8fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *      http://www.apache.org/licenses/LICENSE-2.0
9fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *
10fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * Unless required by applicable law or agreed to in writing, software
11fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * distributed under the License is distributed on an "AS IS" BASIS,
12fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * See the License for the specific language governing permissions and
14fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * limitations under the License.
15fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin */
16fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinpackage dalvik.system;
17fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
18fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport java.lang.reflect.Method;
19fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport java.util.ArrayList;
20fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport java.util.Collection;
21fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport java.util.Collections;
22fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport java.util.List;
23fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport java.util.Set;
24fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport java.util.concurrent.ConcurrentHashMap;
25fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport java.util.function.BiConsumer;
26fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport org.junit.rules.TestRule;
27fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport org.junit.runner.Description;
28fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport org.junit.runners.model.Statement;
29fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
30fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin/**
31fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * Provides support for testing classes that use {@link CloseGuard} in order to detect resource
32fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * leakages.
33fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *
34fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * <p>This class should not be used directly by tests as that will prevent them from being
35fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * compilable and testable on OpenJDK platform. Instead they should use
36fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * {@code libcore.junit.util.ResourceLeakageDetector} which accesses the capabilities of this using
37fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * reflection and if it cannot find it (because it is running on OpenJDK) then it will just skip
38fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * leakage detection.
39fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *
40fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * <p>This provides two entry points that are accessed reflectively:
41fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * <ul>
42fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * <li>
43fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * <p>The {@link #getRule()} method. This returns a {@link TestRule} that will fail a test if it
44fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * detects any resources that were allocated during the test but were not released.
45fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *
46fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * <p>This only tracks resources that were allocated on the test thread, although it does not care
47fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * what thread they were released on. This avoids flaky false positives where a background thread
48fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * allocates a resource during a test but releases it after the test.
49fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *
50fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * <p>It is still possible to have a false positive in the case where the test causes a caching
51fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * mechanism to open a resource and hold it open past the end of the test. In that case if there is
52fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * no way to clear the cached data then it should be relatively simple to move the code that invokes
53fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * the caching mechanism to outside the scope of this rule. i.e.
54fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *
55fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * <pre>{@code
56fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *     @Rule
57fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *     public final TestRule ruleChain = org.junit.rules.RuleChain
58fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *         .outerRule(new ...invoke caching mechanism...)
59fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin *         .around(CloseGuardSupport.getRule());
60fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * }</pre>
61fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * </li>
62fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * <li>
63fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * <p>The {@link #getFinalizerChecker()} method. This returns a {@link BiConsumer} that takes an
64fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * object that owns resources and an expected number of unreleased resources. It will call the
65fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * {@link Object#finalize()} method on the object using reflection and throw an
66fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * {@link AssertionError} if the number of reported unreleased resources does not match the
67fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * expected number.
68fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * </li>
69fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin * </ul>
70fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin */
71fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinpublic class CloseGuardSupport {
72fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
73fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    private static final TestRule CLOSE_GUARD_RULE = new FailTestWhenResourcesNotClosedRule();
74fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
75fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    /**
76fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * Get a {@link TestRule} that will detect when resources that use the {@link CloseGuard}
77fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * mechanism are not cleaned up properly by a test.
78fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     *
79fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * <p>If the {@link CloseGuard} mechanism is not supported, e.g. on OpenJDK, then the returned
80fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * rule does nothing.
81fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     */
82fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    public static TestRule getRule() {
83fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        return CLOSE_GUARD_RULE;
84fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    }
85fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
86fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    private CloseGuardSupport() {
87fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    }
88fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
89fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    /**
90fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * Fails a test when resources are not cleaned up properly.
91fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     */
92fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    private static class FailTestWhenResourcesNotClosedRule implements TestRule {
93fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        /**
94fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin         * Returns a {@link Statement} that will fail the test if it ends with unreleased resources.
95fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin         * @param base the test to be run.
96fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin         */
97fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        public Statement apply(Statement base, Description description) {
98fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            return new Statement() {
99fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                @Override
100fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                public void evaluate() throws Throwable {
101fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    // Get the previous tracker so that it can be restored afterwards.
102fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    CloseGuard.Tracker previousTracker = CloseGuard.getTracker();
103fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    // Get the previous enabled state so that it can be restored afterwards.
104fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    boolean previousEnabled = CloseGuard.isEnabled();
105fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    TestCloseGuardTracker tracker = new TestCloseGuardTracker();
106fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    Throwable thrown = null;
107fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    try {
108fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        // Set the test tracker and enable close guard detection.
109fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        CloseGuard.setTracker(tracker);
110fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        CloseGuard.setEnabled(true);
111fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        base.evaluate();
112fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    } catch (Throwable throwable) {
113fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        // Catch and remember the throwable so that it can be rethrown in the
114fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        // finally block.
115fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        thrown = throwable;
116fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    } finally {
117fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        // Restore the previous tracker and enabled state.
118fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        CloseGuard.setEnabled(previousEnabled);
119fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        CloseGuard.setTracker(previousTracker);
120fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
121fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        Collection<Throwable> allocationSites =
122fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                                tracker.getAllocationSitesForUnreleasedResources();
123fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        if (!allocationSites.isEmpty()) {
124fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                            if (thrown == null) {
125fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                                thrown = new IllegalStateException(
126fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                                        "Unreleased resources found in test");
127fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                            }
128fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                            for (Throwable allocationSite : allocationSites) {
129fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                                thrown.addSuppressed(allocationSite);
130fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                            }
131fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        }
132fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        if (thrown != null) {
133fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                            throw thrown;
134fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        }
135fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    }
136fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                }
137fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            };
138fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        }
139fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    }
140fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
141fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    /**
142fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * A tracker that keeps a record of the allocation sites for all resources allocated but not
143fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * yet released.
144fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     *
145fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * <p>It only tracks resources allocated for the test thread.
146fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     */
147fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    private static class TestCloseGuardTracker implements CloseGuard.Tracker {
148fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
149fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        /**
150fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin         * A set would be preferable but this is the closest that matches the concurrency
151fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin         * requirements for the use case which prioritise speed of addition and removal over
152fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin         * iteration and access.
153fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin         */
154fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        private final Set<Throwable> allocationSites =
155fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                Collections.newSetFromMap(new ConcurrentHashMap<>());
156fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
157fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        private final Thread testThread = Thread.currentThread();
158fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
159fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        @Override
160fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        public void open(Throwable allocationSite) {
161fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            if (Thread.currentThread() == testThread) {
162fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                allocationSites.add(allocationSite);
163fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            }
164fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        }
165fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
166fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        @Override
167fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        public void close(Throwable allocationSite) {
168fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            // Closing the resource twice could pass null into here.
169fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            if (allocationSite != null) {
170fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                allocationSites.remove(allocationSite);
171fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            }
172fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        }
173fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
174fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        /**
175fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin         * Get the collection of allocation sites for any unreleased resources.
176fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin         */
177fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        Collection<Throwable> getAllocationSitesForUnreleasedResources() {
178fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            return new ArrayList<>(allocationSites);
179fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        }
180fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    }
181fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
182fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    private static final BiConsumer<Object, Integer> FINALIZER_CHECKER
183fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            = new BiConsumer<Object, Integer>() {
184fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        @Override
185fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        public void accept(Object resourceOwner, Integer expectedCount) {
186fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            finalizerChecker(resourceOwner, expectedCount);
187fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        }
188fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    };
189fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
190fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    /**
191fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * Get access to a {@link BiConsumer} that will determine how many unreleased resources the
192fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * first parameter owns and throw a {@link AssertionError} if that does not match the
193fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * expected number of resources specified by the second parameter.
194fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     *
195fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * <p>This uses a {@link BiConsumer} as it is a standard interface that is available in all
196fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * environments. That helps avoid the caller from having compile time dependencies on this
197fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * class which will not be available on OpenJDK.
198fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     */
199fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    public static BiConsumer<Object, Integer> getFinalizerChecker() {
200fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        return FINALIZER_CHECKER;
201fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    }
202fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
203fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    /**
204fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * Checks that the supplied {@code resourceOwner} has overridden the {@link Object#finalize()}
205fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * method and uses {@link CloseGuard#warnIfOpen()} correctly to detect when the resource is
206fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * not released.
207fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     *
208fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * @param resourceOwner the owner of the resource protected by {@link CloseGuard}.
209fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * @param expectedCount the expected number of unreleased resources to be held by the owner.
210fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     *
211fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     */
212fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    private static void finalizerChecker(Object resourceOwner, int expectedCount) {
213fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        Class<?> clazz = resourceOwner.getClass();
214fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        Method finalizer = null;
215fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        while (clazz != null && clazz != Object.class) {
216fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            try {
217fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                finalizer = clazz.getDeclaredMethod("finalize");
218fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                break;
219fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            } catch (NoSuchMethodException e) {
220fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                // Carry on up the class hierarchy.
221fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                clazz = clazz.getSuperclass();
222fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            }
223fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        }
224fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
225fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        if (finalizer == null) {
226fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            // No finalizer method could be found.
227fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            throw new AssertionError("Class " + resourceOwner.getClass().getName()
228fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    + " does not have a finalize() method");
229fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        }
230fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
231fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        // Make the method accessible.
232fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        finalizer.setAccessible(true);
233fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
234fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        CloseGuard.Reporter oldReporter = CloseGuard.getReporter();
235fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        try {
236fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            CollectingReporter reporter = new CollectingReporter();
237fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            CloseGuard.setReporter(reporter);
238fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
239fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            // Invoke the finalizer to cause it to get CloseGuard to report a problem if it has
240fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            // not yet been closed.
241fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            try {
242fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                finalizer.invoke(resourceOwner);
243fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            } catch (ReflectiveOperationException e) {
244fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                throw new AssertionError(
245fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                        "Could not invoke the finalizer() method on " + resourceOwner, e);
246fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            }
247fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
248fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            reporter.assertUnreleasedResources(expectedCount);
249fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        } finally {
250fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            CloseGuard.setReporter(oldReporter);
251fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        }
252fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    }
253fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
254fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    /**
255fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     * A {@link CloseGuard.Reporter} that collects any reports about unreleased resources.
256fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin     */
257fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    private static class CollectingReporter implements CloseGuard.Reporter {
258fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
259fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        private final Thread callingThread = Thread.currentThread();
260fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
261fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        private final List<Throwable> unreleasedResourceAllocationSites = new ArrayList<>();
262fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
263fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        @Override
264fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        public void report(String message, Throwable allocationSite) {
265fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            // Only care about resources that are not reported on this thread.
266fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            if (callingThread == Thread.currentThread()) {
267fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                unreleasedResourceAllocationSites.add(allocationSite);
268fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            }
269fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        }
270fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
271fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        void assertUnreleasedResources(int expectedCount) {
272fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            int unreleasedResourceCount = unreleasedResourceAllocationSites.size();
273fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            if (unreleasedResourceCount == expectedCount) {
274fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                return;
275fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            }
276fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin
277fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            AssertionError error = new AssertionError(
278fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                    "Expected " + expectedCount + " unreleased resources, found "
279fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                            + unreleasedResourceCount + "; see suppressed exceptions for details");
280fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            for (Throwable unreleasedResourceAllocationSite : unreleasedResourceAllocationSites) {
281fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin                error.addSuppressed(unreleasedResourceAllocationSite);
282fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            }
283fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin            throw error;
284fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin        }
285fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin    }
286fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffin}
287