1 /*
2  * Copyright (c) 2016, 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 /*
25  * @test
26  * @bug 6479237
27  * @summary Test the format of StackTraceElement::toString and its serial form
28  * @modules java.logging
29  *
30  * @run main SerialTest
31  */
32 
33 import java.io.BufferedInputStream;
34 import java.io.BufferedOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.ObjectInputStream;
38 import java.io.ObjectOutputStream;
39 import java.io.OutputStream;
40 import java.io.UncheckedIOException;
41 import java.lang.reflect.Method;
42 import java.net.MalformedURLException;
43 import java.net.URL;
44 import java.net.URLClassLoader;
45 import java.nio.file.Files;
46 import java.nio.file.Path;
47 import java.nio.file.Paths;
48 import java.util.Arrays;
49 import java.util.logging.Logger;
50 
51 public class SerialTest {
52     private static final Path SER_DIR = Paths.get("sers");
53     private static final String JAVA_BASE = "java.base";
54     private static final String JAVA_LOGGING = "java.logging";
55 
56     private static boolean isImage;
57 
main(String... args)58     public static void main(String... args) throws Exception {
59         Files.createDirectories(SER_DIR);
60 
61         // detect if exploded image build
62         Path home = Paths.get(System.getProperty("java.home"));
63         isImage = Files.exists(home.resolve("lib").resolve("modules"));
64 
65         // test stack trace from built-in loaders
66         try {
67             Logger.getLogger(null);
68         } catch (NullPointerException e) {
69             Arrays.stream(e.getStackTrace())
70                   .filter(ste -> ste.getClassName().startsWith("java.util.logging.") ||
71                                  ste.getClassName().equals("SerialTest"))
72                   .forEach(SerialTest::test);
73         }
74 
75         // test stack trace with class loader name from other class loader
76         Loader loader = new Loader("myloader");
77         Class<?> cls = Class.forName("SerialTest", true, loader);
78         Method method = cls.getMethod("throwException");
79         StackTraceElement ste = (StackTraceElement)method.invoke(null);
80         test(ste, loader);
81 
82         // verify the class loader name and in the stack trace
83         if (!cls.getClassLoader().getName().equals("myloader.hacked")) {
84             throw new RuntimeException("Unexpected loader name: " +
85                 cls.getClassLoader().getName());
86         }
87         if (!ste.getClassLoaderName().equals("myloader")) {
88             throw new RuntimeException("Unexpected loader name: " +
89                 ste.getClassLoaderName());
90         }
91     }
92 
test(StackTraceElement ste)93     private static void test(StackTraceElement ste) {
94         test(ste, null);
95     }
96 
test(StackTraceElement ste, ClassLoader loader)97     private static void test(StackTraceElement ste, ClassLoader loader) {
98         try {
99             SerialTest serialTest = new SerialTest(ste);
100             StackTraceElement ste2 = serialTest.serialize().deserialize();
101             System.out.println(ste2);
102             // verify StackTraceElement::toString returns the same string
103             if (!ste.equals(ste2) || !ste.toString().equals(ste2.toString())) {
104                 throw new RuntimeException(ste + " != " + ste2);
105             }
106 
107             String mn = ste.getModuleName();
108             if (mn != null) {
109                 switch (mn) {
110                     case JAVA_BASE:
111                     case JAVA_LOGGING:
112                         checkNamedModule(ste, loader, false);
113                         break;
114                     default:  // ignore
115                 }
116             } else {
117                 checkUnnamedModule(ste, loader);
118             }
119         } catch (IOException e) {
120             throw new UncheckedIOException(e);
121         }
122     }
123 
checkUnnamedModule(StackTraceElement ste, ClassLoader loader)124     private static void checkUnnamedModule(StackTraceElement ste, ClassLoader loader) {
125         String mn = ste.getModuleName();
126         String s = ste.toString();
127         int i = s.indexOf('/');
128 
129         if (mn != null) {
130             throw new RuntimeException("expected null but got " + mn);
131         }
132 
133         if (loader != null) {
134             // Expect <loader>//<classname>.<method>(<src>:<ln>)
135             if (i <= 0) {
136                 throw new RuntimeException("loader name missing: " + s);
137             }
138             if (!getLoaderName(loader).equals(s.substring(0, i))) {
139                 throw new RuntimeException("unexpected loader name: " + s);
140             }
141             int j = s.substring(i+1).indexOf('/');
142             if (j != 0) {
143                 throw new RuntimeException("unexpected element for unnamed module: " + s);
144             }
145         }
146     }
147 
148     /*
149      * Loader::getName is overridden to return some other name
150      */
getLoaderName(ClassLoader loader)151     private static String getLoaderName(ClassLoader loader) {
152         if (loader == null)
153             return "";
154 
155         if (loader instanceof Loader) {
156             return ((Loader) loader).name;
157         } else {
158             return loader.getName();
159         }
160     }
161 
checkNamedModule(StackTraceElement ste, ClassLoader loader, boolean showVersion)162     private static void checkNamedModule(StackTraceElement ste,
163                                          ClassLoader loader,
164                                          boolean showVersion) {
165         String loaderName = getLoaderName(loader);
166         String mn = ste.getModuleName();
167         String s = ste.toString();
168         int i = s.indexOf('/');
169 
170         if (mn == null) {
171             throw new RuntimeException("expected module name: " + s);
172         }
173 
174         if (i <= 0) {
175             throw new RuntimeException("module name missing: " + s);
176         }
177 
178         // Expect <module>/<classname>.<method>(<src>:<ln>)
179         if (!loaderName.isEmpty()) {
180             throw new IllegalArgumentException(loaderName);
181         }
182 
183         // <module>: name@version
184         int j = s.indexOf('@');
185         if ((showVersion && j <= 0) || (!showVersion && j >= 0)) {
186             throw new RuntimeException("unexpected version: " + s);
187         }
188 
189         String name = j < 0 ? s.substring(0, i) : s.substring(0, j);
190         if (!name.equals(mn)) {
191             throw new RuntimeException("unexpected module name: " + s);
192         }
193     }
194 
195     private final Path ser;
196     private final StackTraceElement ste;
197     SerialTest(StackTraceElement ste) throws IOException {
198         this.ser = Files.createTempFile(SER_DIR, "SerialTest", ".ser");
199         this.ste = ste;
200     }
201 
202     private StackTraceElement deserialize() throws IOException {
203         try (InputStream in = Files.newInputStream(ser);
204              BufferedInputStream bis = new BufferedInputStream(in);
205              ObjectInputStream ois = new ObjectInputStream(bis)) {
206             return (StackTraceElement)ois.readObject();
207         } catch (ClassNotFoundException e) {
208             throw new RuntimeException(e);
209         }
210     }
211 
212     private SerialTest serialize() throws IOException {
213         try (OutputStream out = Files.newOutputStream(ser);
214              BufferedOutputStream bos = new BufferedOutputStream(out);
215             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
216             oos.writeObject(ste);
217         }
218         return this;
219     }
220 
221 
222     public static StackTraceElement throwException() {
223         try {
224             Integer.parseInt(null);
225         } catch (NumberFormatException e) {
226             return Arrays.stream(e.getStackTrace())
227                 .filter(ste -> ste.getMethodName().equals("throwException"))
228                 .findFirst().get();
229         }
230         return null;
231     }
232 
233     public static class Loader extends URLClassLoader {
234         final String name;
Loader(String name)235         Loader(String name) throws MalformedURLException {
236             super(name, new URL[] { testClassesURL() } , null);
237             this.name = name;
238         }
239 
testClassesURL()240         private static URL testClassesURL() throws MalformedURLException {
241             Path path = Paths.get(System.getProperty("test.classes"));
242             return path.toUri().toURL();
243         }
244 
getName()245         public String getName() {
246             return name + ".hacked";
247         }
248     }
249 }
250