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