1 /*
2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /* @test
25  * @bug 8185582 8197989
26  * @modules java.base/java.util.zip:open java.base/jdk.internal.vm.annotation
27  * @summary Check the resources of Inflater, Deflater and ZipFile are always
28  *          cleaned/released when the instance is not unreachable
29  */
30 
31 import java.io.*;
32 import java.lang.reflect.*;
33 import java.util.*;
34 import java.util.concurrent.CountDownLatch;
35 import java.util.concurrent.TimeUnit;
36 import java.util.zip.*;
37 import jdk.internal.vm.annotation.DontInline;
38 import static java.nio.charset.StandardCharsets.US_ASCII;
39 
40 public class TestCleaner {
41 
main(String[] args)42     public static void main(String[] args) throws Throwable {
43         testDeInflater();
44         testZipFile();
45     }
46 
addrOf(Object obj)47     private static long addrOf(Object obj) {
48         try {
49             Field addr = obj.getClass().getDeclaredField("address");
50             if (!addr.trySetAccessible()) {
51                 return -1;
52             }
53             return addr.getLong(obj);
54         } catch (Exception x) {
55             return -1;
56         }
57     }
58 
59     private static class SubclassedInflater extends Inflater {
60         CountDownLatch endCountDown;
61 
SubclassedInflater(CountDownLatch endCountDown)62         SubclassedInflater(CountDownLatch endCountDown) {
63             this.endCountDown = endCountDown;
64         }
65 
66         @Override
end()67         public void end() {
68             super.end();
69             endCountDown.countDown();
70         }
71     }
72 
73     private static class SubclassedDeflater extends Deflater {
74         CountDownLatch endCountDown;
75 
SubclassedDeflater(CountDownLatch endCountDown)76         SubclassedDeflater(CountDownLatch endCountDown) {
77             this.endCountDown = endCountDown;
78         }
79 
80         @Override
end()81         public void end() {
82             super.end();
83             endCountDown.countDown();
84         }
85     }
86 
87     // verify the "native resource" of In/Deflater has been cleaned
testDeInflater()88     private static void testDeInflater() throws Throwable {
89         Field zsRefDef = Deflater.class.getDeclaredField("zsRef");
90         Field zsRefInf = Inflater.class.getDeclaredField("zsRef");
91         if (!zsRefDef.trySetAccessible() || !zsRefInf.trySetAccessible()) {
92             throw new RuntimeException("'zsRef' is not accesible");
93         }
94         if (addrOf(zsRefDef.get(new Deflater())) == -1 ||
95             addrOf(zsRefInf.get(new Inflater())) == -1) {
96             throw new RuntimeException("'addr' is not accesible");
97         }
98         List<Object> list = new ArrayList<>();
99         byte[] buf1 = new byte[1024];
100         byte[] buf2 = new byte[1024];
101         for (int i = 0; i < 10; i++) {
102             var def = new Deflater();
103             list.add(zsRefDef.get(def));
104             def.setInput("hello".getBytes());
105             def.finish();
106             int n = def.deflate(buf1);
107 
108             var inf = new Inflater();
109             list.add(zsRefInf.get(inf));
110             inf.setInput(buf1, 0, n);
111             n = inf.inflate(buf2);
112             if (!"hello".equals(new String(buf2, 0, n))) {
113                 throw new RuntimeException("compression/decompression failed");
114             }
115         }
116 
117         int n = 10;
118         long cnt = list.size();
119         while (n-- > 0 && cnt != 0) {
120             Thread.sleep(100);
121             System.gc();
122             cnt = list.stream().filter(o -> addrOf(o) != 0).count();
123         }
124         if (cnt != 0)
125             throw new RuntimeException("cleaner failed to clean : " + cnt);
126 
127         // test subclassed Deflater/Inflater, for behavioral compatibility.
128         // should be removed if the finalize() method is finally removed.
129         var endCountDown = new CountDownLatch(20);
130         for (int i = 0; i < 10; i++) {
131             var def = new SubclassedDeflater(endCountDown);
132             def.setInput("hello".getBytes());
133             def.finish();
134             n = def.deflate(buf1);
135 
136             var inf = new SubclassedInflater(endCountDown);
137             inf.setInput(buf1, 0, n);
138             n = inf.inflate(buf2);
139             if (!"hello".equals(new String(buf2, 0, n))) {
140                 throw new RuntimeException("compression/decompression failed");
141             }
142         }
143         while (!endCountDown.await(10, TimeUnit.MILLISECONDS)) {
144             System.gc();
145         }
146         if (endCountDown.getCount() != 0)
147             throw new RuntimeException("finalizer failed to clean : " +
148                 endCountDown.getCount());
149     }
150 
151     private static class SubclassedZipFile extends ZipFile {
152         CountDownLatch closeCountDown;
153 
SubclassedZipFile(File f, CountDownLatch closeCountDown)154         SubclassedZipFile(File f, CountDownLatch closeCountDown)
155             throws IOException {
156             super(f);
157             this.closeCountDown = closeCountDown;
158         }
159 
160         @Override
close()161         public void close() throws IOException {
162             closeCountDown.countDown();
163             super.close();
164         }
165     }
166 
167     @DontInline
openAndCloseZipFile(File zip)168     private static Object openAndCloseZipFile(File zip) throws Throwable {
169         try {
170             try (var fos = new FileOutputStream(zip);
171                  var zos = new ZipOutputStream(fos)) {
172                 zos.putNextEntry(new ZipEntry("hello"));
173                 zos.write("hello".getBytes(US_ASCII));
174                 zos.closeEntry();
175             }
176 
177             var zf = new ZipFile(zip);
178             var es = zf.entries();
179             while (es.hasMoreElements()) {
180                 zf.getInputStream(es.nextElement()).read();
181             }
182 
183             Field fieldRes = ZipFile.class.getDeclaredField("res");
184             if (!fieldRes.trySetAccessible()) {
185                 throw new RuntimeException("'ZipFile.res' is not accesible");
186             }
187             Object zfRes = fieldRes.get(zf);
188             if (zfRes == null) {
189                 throw new RuntimeException("'ZipFile.res' is null");
190             }
191             Field fieldZsrc = zfRes.getClass().getDeclaredField("zsrc");
192             if (!fieldZsrc.trySetAccessible()) {
193                 throw new RuntimeException("'ZipFile.zsrc' is not accesible");
194             }
195             return fieldZsrc.get(zfRes);
196         } finally {
197             zip.delete();
198         }
199     }
200 
201     @DontInline
openAndCloseSubZipFile(File zip, CountDownLatch closeCountDown)202     private static void openAndCloseSubZipFile(File zip, CountDownLatch closeCountDown)
203         throws Throwable {
204         try {
205             try (var fos = new FileOutputStream(zip);
206                  var zos = new ZipOutputStream(fos)) {
207                 zos.putNextEntry(new ZipEntry("hello"));
208                 zos.write("hello".getBytes(US_ASCII));
209                 zos.closeEntry();
210             }
211             var zf = new SubclassedZipFile(zip, closeCountDown);
212             var es = zf.entries();
213             while (es.hasMoreElements()) {
214                 zf.getInputStream(es.nextElement()).read();
215             }
216             es = null;
217             zf = null;
218         } finally {
219             zip.delete();
220         }
221     }
222 
testZipFile()223     private static void testZipFile() throws Throwable {
224         File dir = new File(System.getProperty("test.dir", "."));
225         File zip = File.createTempFile("testzf", "zip", dir);
226 
227         Object zsrc = openAndCloseZipFile(zip);
228         if (zsrc != null) {
229             Field zfileField = zsrc.getClass().getDeclaredField("zfile");
230             if (!zfileField.trySetAccessible()) {
231                 throw new RuntimeException("'ZipFile.Source.zfile' is not accesible");
232             }
233             //System.out.println("zffile: " +  zfileField.get(zsrc));
234             int n = 10;
235             while (n-- > 0 && zfileField.get(zsrc) != null) {
236                 System.out.println("waiting gc ... " + n);
237                 System.gc();
238                 Thread.sleep(100);
239             }
240             if (zfileField.get(zsrc) != null) {
241                 throw new RuntimeException("cleaner failed to clean zipfile.");
242             }
243         }
244 
245         // test subclassed ZipFile, for behavioral compatibility.
246         // should be removed if the finalize() method is finally removed.
247         var closeCountDown = new CountDownLatch(1);
248         openAndCloseSubZipFile(zip, closeCountDown);
249         while (!closeCountDown.await(10, TimeUnit.MILLISECONDS)) {
250             System.gc();
251         }
252         if (closeCountDown.getCount() != 0)
253             throw new RuntimeException("finalizer failed to clean : " +
254                 closeCountDown.getCount());
255     }
256 }
257