1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package libcore.libcore.util;
18 
19 import junit.framework.TestCase;
20 
21 import libcore.util.NativeAllocationRegistry;
22 
23 public class NativeAllocationRegistryTest extends TestCase {
24 
25     static {
26         System.loadLibrary("javacoretests");
27     }
28 
29     private ClassLoader classLoader = NativeAllocationRegistryTest.class.getClassLoader();
30 
31     private static class TestConfig {
32         public boolean treatAsMalloced;
33         public boolean shareRegistry;
34 
TestConfig(boolean treatAsMalloced, boolean shareRegistry)35         public TestConfig(boolean treatAsMalloced, boolean shareRegistry) {
36             this.shareRegistry = shareRegistry;
37         }
38     }
39 
40     private static class Allocation {
41         public byte[] javaAllocation;
42         public long nativeAllocation;
43     }
44 
45     // Verify that NativeAllocations and their referents are freed before we run
46     // out of space for new allocations.
testNativeAllocation(TestConfig config)47     private void testNativeAllocation(TestConfig config) {
48         if (isNativeBridgedABI()) {
49             // 1. This test is intended to test platform internals, not public API.
50             // 2. The test would fail under native bridge as a side effect of how the tests work:
51             //  - The tests run using the app architecture instead of the platform architecture
52             //  - That scenario will never happen in practice due to (1)
53             // 3. This leaves a hole in testing for the case of native bridge, due to limitations
54             //    in the testing infrastructure from (2).
55             System.logI("Skipping test for native bridged ABI");
56             return;
57         }
58         Runtime.getRuntime().gc();
59         System.runFinalization();
60         long nativeBytes = getNumNativeBytesAllocated();
61         assertEquals("Native bytes already allocated", 0, nativeBytes);
62         long max = Runtime.getRuntime().maxMemory();
63         long total = Runtime.getRuntime().totalMemory();
64         int size = 1024*1024;
65         final int nativeSize = size/2;
66         int javaSize = size/2;
67         int expectedMaxNumAllocations = (int)(max-total)/javaSize;
68         int numSavedAllocations = expectedMaxNumAllocations/2;
69         Allocation[] saved = new Allocation[numSavedAllocations];
70 
71         NativeAllocationRegistry registry = null;
72         int numAllocationsToSimulate = 10 * expectedMaxNumAllocations;
73 
74         // Allocate more native allocations than will fit in memory. This should
75         // not throw OutOfMemoryError because the few allocations we save
76         // references to should easily fit.
77         for (int i = 0; i < numAllocationsToSimulate; i++) {
78             if (!config.shareRegistry || registry == null) {
79                 if (config.treatAsMalloced) {
80                     registry = NativeAllocationRegistry.createMalloced(
81                             classLoader, getNativeFinalizer(), nativeSize);
82                 } else {
83                     registry = NativeAllocationRegistry.createNonmalloced(
84                             classLoader, getNativeFinalizer(), nativeSize);
85                 }
86             }
87 
88             final Allocation alloc = new Allocation();
89             alloc.javaAllocation = new byte[javaSize];
90             alloc.nativeAllocation = doNativeAllocation(nativeSize);
91             registry.registerNativeAllocation(alloc, alloc.nativeAllocation);
92 
93             saved[i%numSavedAllocations] = alloc;
94         }
95 
96         // Verify most of the allocations have been freed.
97         // Since we use fairly large Java objects, this doesn't test the GC triggering
98         // effect; we do that elsewhere.
99         // Since native and java objects have the same size, and we can only have max
100         // Java bytes in use, there should be no more than max native bytes in use,
101         // once all enqueued deallocations have been processed. First make sure
102         // that the ReferenceQueueDaemon has processed all pending requests, and then
103         // check.
104         System.runFinalization();
105         nativeBytes = getNumNativeBytesAllocated();
106         assertTrue("Excessive native bytes still allocated (" + nativeBytes + ")"
107                 + " given max memory of (" + max + ")", nativeBytes <= max);
108         // Check that the array is fully populated, and sufficiently many native bytes
109         // are live.
110         long nativeReachableBytes = numSavedAllocations * nativeSize;
111         for (int i = 0; i < numSavedAllocations; i++) {
112             assertNotNull(saved[i]);
113             assertNotNull(saved[i].javaAllocation);
114             assertTrue(saved[i].nativeAllocation != 0);
115         }
116         assertTrue("Too few native bytes still allocated (" + nativeBytes + "); "
117                 + nativeReachableBytes + " bytes are reachable",
118                 nativeBytes >= nativeReachableBytes);
119     }
120 
testNativeAllocationNonmallocNoSharedRegistry()121     public void testNativeAllocationNonmallocNoSharedRegistry() {
122         testNativeAllocation(new TestConfig(false, false));
123     }
124 
testNativeAllocationNonmallocSharedRegistry()125     public void testNativeAllocationNonmallocSharedRegistry() {
126         testNativeAllocation(new TestConfig(false, true));
127     }
128 
testNativeAllocationMallocNoSharedRegistry()129     public void testNativeAllocationMallocNoSharedRegistry() {
130         testNativeAllocation(new TestConfig(true, false));
131     }
132 
testNativeAllocationMallocSharedRegistry()133     public void testNativeAllocationMallocSharedRegistry() {
134         testNativeAllocation(new TestConfig(true, true));
135     }
136 
testBadSize()137     public void testBadSize() {
138         assertThrowsIllegalArgumentException(new Runnable() {
139             public void run() {
140                 NativeAllocationRegistry registry = new NativeAllocationRegistry(
141                         classLoader, getNativeFinalizer(), -8);
142             }
143         });
144     }
145 
testEarlyFree()146     public void testEarlyFree() {
147         if (isNativeBridgedABI()) {
148             // See the explanation in testNativeAllocation.
149             System.logI("Skipping test for native bridged ABI");
150             return;
151         }
152         long size = 1234;
153         NativeAllocationRegistry registry
154             = new NativeAllocationRegistry(classLoader, getNativeFinalizer(), size);
155         long nativePtr = doNativeAllocation(size);
156         Object referent = new Object();
157         Runnable cleaner = registry.registerNativeAllocation(referent, nativePtr);
158         long numBytesAllocatedBeforeClean = getNumNativeBytesAllocated();
159 
160         // Running the cleaner should cause the native finalizer to run.
161         cleaner.run();
162         long numBytesAllocatedAfterClean = getNumNativeBytesAllocated();
163         assertEquals(numBytesAllocatedBeforeClean - size, numBytesAllocatedAfterClean);
164 
165         // Running the cleaner again should have no effect.
166         cleaner.run();
167         assertEquals(numBytesAllocatedAfterClean, getNumNativeBytesAllocated());
168 
169         // There shouldn't be any problems when the referent object is GC'd.
170         referent = null;
171         Runtime.getRuntime().gc();
172     }
173 
testNullArguments()174     public void testNullArguments() {
175         final NativeAllocationRegistry registry
176             = new NativeAllocationRegistry(classLoader, getNativeFinalizer(), 1024);
177         final long fakeNativePtr = 0x1;
178         final Object referent = new Object();
179 
180         // referent should not be null
181         assertThrowsIllegalArgumentException(new Runnable() {
182             public void run() {
183                 registry.registerNativeAllocation(null, fakeNativePtr);
184             }
185         });
186 
187         // nativePtr should not be null
188         assertThrowsIllegalArgumentException(new Runnable() {
189             public void run() {
190                 registry.registerNativeAllocation(referent, 0);
191             }
192         });
193     }
194 
assertThrowsIllegalArgumentException(Runnable runnable)195     private static void assertThrowsIllegalArgumentException(Runnable runnable) {
196         try {
197             runnable.run();
198         } catch (IllegalArgumentException ex) {
199             return;
200         }
201         fail("Expected IllegalArgumentException, but no exception was thrown.");
202     }
203 
isNativeBridgedABI()204     private static native boolean isNativeBridgedABI();
getNativeFinalizer()205     private static native long getNativeFinalizer();
doNativeAllocation(long size)206     private static native long doNativeAllocation(long size);
getNumNativeBytesAllocated()207     private static native long getNumNativeBytesAllocated();
208 }
209