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 
  33 import javax.xml.bind.JAXBElement;
  34 import java.io.BufferedInputStream;
  35 import java.io.BufferedOutputStream;
  36 import java.io.IOException;
  37 import java.io.InputStream;
  38 import java.io.ObjectInputStream;
  39 import java.io.ObjectOutputStream;
  40 import java.io.OutputStream;
  41 import java.io.UncheckedIOException;
  42 import java.lang.reflect.Method;
  43 import java.net.MalformedURLException;
  44 import java.net.URL;
  45 import java.net.URLClassLoader;
  46 import java.nio.file.Files;
  47 import java.nio.file.Path;
  48 import java.nio.file.Paths;
  49 import java.util.Arrays;
  50 import java.util.logging.Logger;
  51 
  52 public 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 }