1 /*
2  * Copyright (c) 2015, 2019, 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 /*
25  * @test
26  * @summary Unit test for libjimage JIMAGE_Open/Read/Close
27  * @modules java.base/jdk.internal.jimage
28  * @run testng JImageReadTest
29  */
30 
31 import java.io.File;
32 import java.io.FileWriter;
33 import java.io.IOException;
34 import java.nio.ByteBuffer;
35 import java.nio.ByteOrder;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.util.Arrays;
40 
41 import jdk.internal.jimage.BasicImageReader;
42 import jdk.internal.jimage.ImageReader;
43 import jdk.internal.jimage.ImageLocation;
44 
45 import org.testng.annotations.DataProvider;
46 import org.testng.annotations.Optional;
47 import org.testng.annotations.Parameters;
48 import org.testng.annotations.Test;
49 import org.testng.Assert;
50 import org.testng.TestNG;
51 
52 @Test
53 public class JImageReadTest {
54 
55     static String javaHome = System.getProperty("java.home");
56     static Path imageFile = Paths.get(javaHome, "lib", "modules");
57 
58     @DataProvider(name="classes")
loadClasses()59     static Object[][] loadClasses() {
60         return new Object[][] {
61                 {"java.base", "java/lang/String.class"},
62                 {"java.base", "java/lang/Object.class"},
63                 {"java.base", "sun/reflect/generics/tree/TypeArgument.class"},
64                 {"java.base", "sun/net/www/content-types.properties"},
65                 {"java.logging", "java/util/logging/Logger.class"},
66                 {"java.base", "java/NOSUCHCLASS/yyy.class"},    // non-existent
67                 {"NOSUCHMODULE", "java/lang/Class.class"},    // non-existent
68         };
69     }
70 
71     /**
72      * Test a class is correctly accessible from the image in a module.
73      *
74      * @param moduleName the module name
75      * @param className the classname
76      * @throws Exception is thrown if there is a test error
77      */
78     @Test(dataProvider="classes")
test1_ReadClasses(String moduleName, String className)79     public static void test1_ReadClasses(String moduleName, String className) throws Exception {
80         final int classMagic = 0xCAFEBABE;
81 
82         if (!Files.exists(imageFile)) {
83             System.out.printf("Test skipped; no jimage file");
84             return;
85         }
86 
87         BasicImageReader reader = BasicImageReader.open(imageFile);
88         Assert.assertTrue(reader != null, "JIMAGE_Open failed: " + imageFile);
89 
90         ImageLocation location = reader.findLocation(moduleName, className);
91 
92         if (location != null && !location.verify("/" + moduleName + "/" + className)) {
93             location = null;
94         }
95 
96         long size = location != null ? location.getUncompressedSize() : 0;
97 
98         System.out.printf("reading: module: %s, path: %s, size: %d%n",
99                 moduleName, className, size);
100         if (moduleName.contains("NOSUCH") || className.contains("NOSUCH")) {
101             Assert.assertTrue(location == null,
102                     "location found for non-existing module: "
103                     + moduleName
104                     + ", or class: " + className);
105             return;         // no more to test for non-existing class
106         } else {
107             Assert.assertTrue(location != null, "location not found: " + className);
108             Assert.assertTrue(size > 0, "size of should be > 0: " + className);
109         }
110 
111         // positive: read whole class
112         ByteBuffer buffer = reader.getResourceBuffer(location);
113         Assert.assertTrue(buffer != null, "bytes read not equal bytes requested");
114 
115         if (className.endsWith(".class")) {
116             int m = buffer.getInt();
117             Assert.assertEquals(m, classMagic, "Classfile has bad magic number");
118         }
119 
120         reader.close();
121     }
122 
123     /**
124      * For all the resource names, check the name and approximate count.
125      *
126      * @throws IOException thrown if an error occurs
127      */
128     @Test
test2_ImageResources()129     static void test2_ImageResources() throws IOException {
130         if (!Files.exists(imageFile)) {
131             System.out.printf("Test skipped; no jimage file");
132             return;
133         }
134 
135         BasicImageReader reader = BasicImageReader.open(imageFile);
136         Assert.assertTrue(reader != null, "JIMAGE_Open failed: " + imageFile);
137 
138         String[] names = reader.getEntryNames();
139 
140         // Repeat with count available
141         int count = names.length;
142 
143         System.out.printf(" count: %d, a class: %s\n", count, names[0]);
144 
145         int minEntryCount = 16000;
146         Assert.assertTrue(minEntryCount < count,
147                 "unexpected count of entries, count: " + count
148                         + ", min: " + minEntryCount);
149         for (int i = 0; i < count; i++) {
150             checkFullName(names[i]);
151         }
152 
153         reader.close();
154     }
155 
156     static void checkFullName(String path) {
157         if (path.startsWith("/packages") || path.startsWith("/modules")) {
158             return;
159         }
160 
161         int next = 0;
162         String m = null;
163         String p = null;
164         String b = null;
165         String e = null;
166         if (path.startsWith("/")) {
167             next = path.indexOf('/', 1);
168             m = path.substring(1, next);
169             next = next + 1;
170         }
171         int lastSlash = path.lastIndexOf('/');
172         if (lastSlash > next) {
173             // has a parent
174             p = path.substring(next, lastSlash);
175             next = lastSlash + 1;
176         }
177         int period = path.indexOf('.', next);
178         if (period > next) {
179             b = path.substring(next, period);
180             e = path.substring(period + 1);
181         } else {
182             b = path.substring(next);
183         }
184         Assert.assertNotNull(m, "module must be non-empty");
185         Assert.assertNotNull(b, "base name must be non-empty");
186     }
187 
188     /**
189      * Verify that all of the resource names from BasicImageReader
190      * match those returned from the native JIMAGE_Resources iterator.
191      * Names that start with /modules, /packages, and bootmodules.jdata
192      * must appear in the names from JIMAGE_Resource iterator;
193      * from the BasicImageReader they are ignored.
194      */
195     @Test
test3_verifyNames()196     static void test3_verifyNames() {
197         if (!Files.exists(imageFile)) {
198             System.out.printf("Test skipped; no jimage file");
199             return;
200         }
201 
202         try {
203             String[] names = BasicImageReader_Names();
204             //writeNames("/tmp/basic-names.txt", names);              // debug
205 
206             // Read all the names from the native JIMAGE API
207             String[] nativeNames = JIMAGE_Names();
208             //writeNames("/tmp/native-names.txt", nativeNames);       // debug
209 
210 
211             int modCount = 0;
212             int pkgCount = 0;
213             int otherCount = 0;
214             for (String n : nativeNames) {
215                 if (n.startsWith("/modules/")) {
216                     modCount++;
217                 } else if (n.startsWith("/packages/")) {
218                     pkgCount++;
219                 } else {
220                     otherCount++;
221                 }
222             }
223             System.out.printf("native name count: %d, modCount: %d, pkgCount: %d, otherCount: %d%n",
224                     names.length, modCount, pkgCount, otherCount);
225 
226             // Sort and merge the two arrays.  Every name should appear exactly twice.
227             Arrays.sort(names);
228             Arrays.sort(nativeNames);
229             String[] combined = Arrays.copyOf(names, nativeNames.length + names.length);
230             System.arraycopy(nativeNames,0, combined, names.length, nativeNames.length);
231             Arrays.sort(combined);
232             int missing = 0;
233             for (int i = 0; i < combined.length; i++) {
234                 String s = combined[i];
235                 if (isMetaName(s)) {
236                     // Ignore /modules and /packages in BasicImageReader names
237                     continue;
238                 }
239 
240                 if (i < combined.length - 1 && s.equals(combined[i + 1])) {
241                     i++;        // string appears in both java and native
242                     continue;
243                 }
244 
245                 missing++;
246                 int ndx = Arrays.binarySearch(names, s);
247                 String which = (ndx >= 0) ? "java BasicImageReader" : "native JIMAGE_Resources";
248                 System.out.printf("Missing Resource: %s found only via %s%n", s, which);
249             }
250             Assert.assertEquals(missing, 0, "Resources missing");
251 
252         } catch (IOException ioe) {
253             Assert.fail("I/O exception", ioe);
254         }
255     }
256 
257     /**
258      * Return true if the name is one of the meta-data names
259      * @param name a name
260      * @return return true if starts with either /packages or /modules
261      */
isMetaName(String name)262     static boolean isMetaName(String name) {
263         return name.startsWith("/modules")
264                 || name.startsWith("/packages")
265                 || name.startsWith("META-INF")
266                 || name.equals("bootmodules.jdata");
267     }
268 
269     /**
270      * Return all of the names from BasicImageReader.
271      * @return the names returned from BasicImageReader
272      */
BasicImageReader_Names()273     static String[] BasicImageReader_Names() throws IOException {
274         String[] names = null;
275         try (BasicImageReader reader = BasicImageReader.open(imageFile)) {
276             names = reader.getEntryNames();
277         } catch (IOException ioe) {
278             Assert.fail("I/O exception", ioe);
279         }
280         return names;
281     }
282 
283     /**
284      * Returns an array of all of the names returned from JIMAGE_Resources
285      */
JIMAGE_Names()286     static String[] JIMAGE_Names() throws IOException {
287 
288         BasicImageReader reader = BasicImageReader.open(imageFile);
289         Assert.assertNotNull(reader, "JIMAGE_Open failed: " + imageFile);
290 
291         String[] names = reader.getEntryNames();
292 
293         reader.close();
294 
295         return names;
296     }
297 
298     // Write an array of names to a file for debugging
writeNames(String fname, String[] names)299     static void writeNames(String fname, String[] names) throws IOException {
300         try (FileWriter wr = new FileWriter(new File(fname))) {
301             for (String s : names) {
302                 wr.write(s);
303                 wr.write("\n");
304             }
305 
306         }
307         System.out.printf(" %s: %d names%n", fname, names.length);
308     }
309 
310     //@Test
test4_nameTooLong()311     static void test4_nameTooLong() throws IOException {
312         long[] size = new long[1];
313         String moduleName = "FictiousModuleName";
314         String className = String.format("A%09999d", 1);
315 
316         BasicImageReader reader = BasicImageReader.open(imageFile);
317         Assert.assertNotNull(reader, "JIMAGE_Open failed: " + imageFile);
318 
319         String name = "/" + moduleName + "/" + className;
320         ImageLocation location = reader.findLocation(name);
321 
322         if (location != null && !location.verify(name)) {
323             location = null;
324         }
325         Assert.assertTrue(location == null, "Too long name should have failed");
326 
327         reader.close();
328     }
329 
330     /**
331      * Verify that the ImageReader returned by ImageReader.open has the
332      * the requested endianness or fails with an IOException if not.
333      */
334     @Test
test5_imageReaderEndianness()335     static void test5_imageReaderEndianness() throws IOException {
336         ImageReader nativeReader = ImageReader.open(imageFile);
337         Assert.assertEquals(nativeReader.getByteOrder(), ByteOrder.nativeOrder());
338 
339         try {
340             ImageReader leReader = ImageReader.open(imageFile, ByteOrder.LITTLE_ENDIAN);
341             Assert.assertEquals(leReader.getByteOrder(), ByteOrder.LITTLE_ENDIAN);
342             leReader.close();
343         } catch (IOException io) {
344             // IOException expected if LITTLE_ENDIAN not the nativeOrder()
345             Assert.assertNotEquals(ByteOrder.nativeOrder(), ByteOrder.LITTLE_ENDIAN);
346         }
347 
348         try {
349             ImageReader beReader = ImageReader.open(imageFile, ByteOrder.BIG_ENDIAN);
350             Assert.assertEquals(beReader.getByteOrder(), ByteOrder.BIG_ENDIAN);
351             beReader.close();
352         } catch (IOException io) {
353             // IOException expected if LITTLE_ENDIAN not the nativeOrder()
354             Assert.assertNotEquals(ByteOrder.nativeOrder(), ByteOrder.BIG_ENDIAN);
355         }
356 
357         nativeReader.close();
358     }
359     // main method to run standalone from jtreg
360 
361     @Test(enabled=false)
362     @Parameters({"x"})
363     @SuppressWarnings("raw_types")
main(@ptional String[] args)364     public static void main(@Optional String[] args) {
365         Class<?>[] testclass = { JImageReadTest.class};
366         TestNG testng = new TestNG();
367         testng.setTestClasses(testclass);
368         testng.run();
369     }
370 
371 }
372