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 package test.java.util.zip.ZipFile;
32 
33 import java.io.*;
34 import java.lang.reflect.*;
35 import java.nio.file.Files;
36 import java.nio.file.Path;
37 import java.util.*;
38 import java.util.zip.*;
39 import static java.nio.charset.StandardCharsets.US_ASCII;
40 
41 public class TestCleaner {
42 
main(String[] args)43     public static void main(String[] args) throws Throwable {
44         testDeInflater();
45         testZipFile();
46     }
47 
addrOf(Object obj)48     private static long addrOf(Object obj) {
49         try {
50             Field addr = obj.getClass().getDeclaredField("address");
51             // Android-changed: trySetAccessible is not yet available.
52             // if (!addr.trySetAccessible()) {
53             //     return -1;
54             // }
55             addr.setAccessible(true);
56             return addr.getLong(obj);
57         } catch (Exception x) {
58             return -1;
59         }
60     }
61 
62     // verify the "native resource" of In/Deflater has been cleaned
testDeInflater()63     private static void testDeInflater() throws Throwable {
64         Field zsRefDef = Deflater.class.getDeclaredField("zsRef");
65         Field zsRefInf = Inflater.class.getDeclaredField("zsRef");
66         // Android-changed: trySetAccessible is not yet available.
67         // if (!zsRefDef.trySetAccessible() || !zsRefInf.trySetAccessible()) {
68         //     throw new RuntimeException("'zsRef' is not accesible");
69         // }
70         zsRefDef.setAccessible(true);
71         zsRefInf.setAccessible(true);
72 
73         if (addrOf(zsRefDef.get(new Deflater())) == -1 ||
74             addrOf(zsRefInf.get(new Inflater())) == -1) {
75             throw new RuntimeException("'addr' is not accesible");
76         }
77         List<Object> list = new ArrayList<>();
78         byte[] buf1 = new byte[1024];
79         byte[] buf2 = new byte[1024];
80         for (int i = 0; i < 10; i++) {
81             var def = new Deflater();
82             list.add(zsRefDef.get(def));
83             def.setInput("hello".getBytes());
84             def.finish();
85             int n = def.deflate(buf1);
86 
87             var inf = new Inflater();
88             list.add(zsRefInf.get(inf));
89             inf.setInput(buf1, 0, n);
90             n = inf.inflate(buf2);
91             if (!"hello".equals(new String(buf2, 0, n))) {
92                 throw new RuntimeException("compression/decompression failed");
93             }
94         }
95 
96         int n = 10;
97         long cnt = list.size();
98         while (n-- > 0 && cnt != 0) {
99             Thread.sleep(100);
100             // Android-changed: System.gc() does not always trigger GC.
101             // System.gc();
102             Runtime.getRuntime().gc();
103             cnt = list.stream().filter(o -> addrOf(o) != 0).count();
104         }
105         if (cnt != 0)
106             throw new RuntimeException("cleaner failed to clean : " + cnt);
107 
108     }
109 
110     // Android-removed: @DontInline is not available on Android.
111     // @DontInline
openAndCloseZipFile(File zip)112     private static Object openAndCloseZipFile(File zip) throws Throwable {
113         try {
114             try (var fos = new FileOutputStream(zip);
115                  var zos = new ZipOutputStream(fos)) {
116                 zos.putNextEntry(new ZipEntry("hello"));
117                 zos.write("hello".getBytes(US_ASCII));
118                 zos.closeEntry();
119             }
120 
121             var zf = new ZipFile(zip);
122             var es = zf.entries();
123             while (es.hasMoreElements()) {
124                 zf.getInputStream(es.nextElement()).read();
125             }
126 
127             Field fieldRes = ZipFile.class.getDeclaredField("res");
128             // Android-changed: trySetAccessible is not yet available.
129             // if (!fieldRes.trySetAccessible()) {
130             //     throw new RuntimeException("'ZipFile.res' is not accesible");
131             // }
132             fieldRes.setAccessible(true);
133             Object zfRes = fieldRes.get(zf);
134             if (zfRes == null) {
135                 throw new RuntimeException("'ZipFile.res' is null");
136             }
137             Field fieldZsrc = zfRes.getClass().getDeclaredField("zsrc");
138             // Android-changed: trySetAccessible is not yet available.
139             // if (!fieldZsrc.trySetAccessible()) {
140             //     throw new RuntimeException("'ZipFile.zsrc' is not accesible");
141             // }
142             fieldZsrc.setAccessible(true);
143             return fieldZsrc.get(zfRes);
144         } finally {
145             zip.delete();
146         }
147     }
148 
149 
testZipFile()150     private static void testZipFile() throws Throwable {
151         // Android-changed: property is not available, create temp dir explicitly.
152         // File dir = new File(System.getProperty("test.dir", "."));
153         Path dir = Files.createTempDirectory("zip-test-dir");
154         File zip = File.createTempFile("testzf", "zip", dir.toFile());
155 
156         Object zsrc = openAndCloseZipFile(zip);
157         if (zsrc != null) {
158             Field zfileField = zsrc.getClass().getDeclaredField("zfile");
159             // Android-changed: trySetAccessible is not yet available.
160             // if (!zfileField.trySetAccessible()) {
161             //     throw new RuntimeException("'ZipFile.Source.zfile' is not accesible");
162             // }
163             zfileField.setAccessible(true);
164             //System.out.println("zffile: " +  zfileField.get(zsrc));
165             int n = 10;
166             while (n-- > 0 && zfileField.get(zsrc) != null) {
167                 System.out.println("waiting gc ... " + n);
168                 // Android-changed: System.gc() does not always trigger GC.
169                 // System.gc();
170                 Runtime.getRuntime().gc();
171                 Thread.sleep(100);
172             }
173             if (zfileField.get(zsrc) != null) {
174                 throw new RuntimeException("cleaner failed to clean zipfile.");
175             }
176         }
177     }
178 }
179