1 /* 2 * Copyright (c) 2013, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package java.lang.invoke; 26 27 import sun.util.logging.PlatformLogger; 28 29 import java.io.FilePermission; 30 import java.nio.file.Files; 31 import java.nio.file.InvalidPathException; 32 import java.nio.file.Path; 33 import java.nio.file.Paths; 34 import java.security.AccessController; 35 import java.security.PrivilegedAction; 36 import java.util.Objects; 37 import java.util.concurrent.atomic.AtomicBoolean; 38 39 /** 40 * Helper class used by InnerClassLambdaMetafactory to log generated classes 41 * 42 * @implNote 43 * <p> Because this class is called by LambdaMetafactory, make use 44 * of lambda lead to recursive calls cause stack overflow. 45 */ 46 final class ProxyClassesDumper { 47 private static final char[] HEX = { 48 '0', '1', '2', '3', '4', '5', '6', '7', 49 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' 50 }; 51 private static final char[] BAD_CHARS = { 52 '\\', ':', '*', '?', '"', '<', '>', '|' 53 }; 54 private static final String[] REPLACEMENT = { 55 "%5C", "%3A", "%2A", "%3F", "%22", "%3C", "%3E", "%7C" 56 }; 57 58 private final Path dumpDir; 59 60 public static ProxyClassesDumper getInstance(String path) { 61 if (null == path) { 62 return null; 63 } 64 try { 65 path = path.trim(); 66 final Path dir = Paths.get(path.length() == 0 ? "." : path); 67 AccessController.doPrivileged(new PrivilegedAction<>() { 68 @Override 69 public Void run() { 70 validateDumpDir(dir); 71 return null; 72 } 73 }, null, new FilePermission("<<ALL FILES>>", "read, write")); 74 return new ProxyClassesDumper(dir); 75 } catch (InvalidPathException ex) { 76 PlatformLogger.getLogger(ProxyClassesDumper.class.getName()) 77 .warning("Path " + path + " is not valid - dumping disabled", ex); 78 } catch (IllegalArgumentException iae) { 79 PlatformLogger.getLogger(ProxyClassesDumper.class.getName()) 80 .warning(iae.getMessage() + " - dumping disabled"); 81 } 82 return null; 83 } 84 85 private ProxyClassesDumper(Path path) { 86 dumpDir = Objects.requireNonNull(path); 87 } 88 89 private static void validateDumpDir(Path path) { 90 if (!Files.exists(path)) { 91 throw new IllegalArgumentException("Directory " + path + " does not exist"); 92 } else if (!Files.isDirectory(path)) { 93 throw new IllegalArgumentException("Path " + path + " is not a directory"); 94 } else if (!Files.isWritable(path)) { 95 throw new IllegalArgumentException("Directory " + path + " is not writable"); 96 } 97 } 98 99 public static String encodeForFilename(String className) { 100 final int len = className.length(); 101 StringBuilder sb = new StringBuilder(len); 102 103 for (int i = 0; i < len; i++) { 104 char c = className.charAt(i); 105 // control characters 106 if (c <= 31) { 107 sb.append('%'); 108 sb.append(HEX[c >> 4 & 0x0F]); 109 sb.append(HEX[c & 0x0F]); 110 } else { 111 int j = 0; 112 for (; j < BAD_CHARS.length; j++) { 113 if (c == BAD_CHARS[j]) { 114 sb.append(REPLACEMENT[j]); 115 break; 116 } 117 } 118 if (j >= BAD_CHARS.length) { 119 sb.append(c); 120 } 121 } 122 } 123 124 return sb.toString(); 125 } 126 127 public void dumpClass(String className, final byte[] classBytes) { 128 Path file; 129 try { 130 file = dumpDir.resolve(encodeForFilename(className) + ".class"); 131 } catch (InvalidPathException ex) { 132 PlatformLogger.getLogger(ProxyClassesDumper.class.getName()) 133 .warning("Invalid path for class " + className); 134 return; 135 } 136 137 try { 138 Path dir = file.getParent(); 139 Files.createDirectories(dir); 140 Files.write(file, classBytes); 141 } catch (Exception ignore) { 142 PlatformLogger.getLogger(ProxyClassesDumper.class.getName()) 143 .warning("Exception writing to path at " + file.toString()); 144 // simply don't care if this operation failed 145 } 146 } 147 }