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. 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 jdk.tools.jlink.internal.plugins; 26 27 import java.io.ByteArrayInputStream; 28 import java.lang.reflect.Method; 29 import java.util.Arrays; 30 import java.util.Collections; 31 import java.util.EnumSet; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Set; 35 import java.util.stream.Collectors; 36 import jdk.internal.org.objectweb.asm.ClassWriter; 37 import jdk.internal.org.objectweb.asm.Label; 38 import jdk.internal.org.objectweb.asm.MethodVisitor; 39 import jdk.internal.org.objectweb.asm.Opcodes; 40 import jdk.internal.org.objectweb.asm.Type; 41 import static jdk.internal.org.objectweb.asm.Opcodes.*; 42 import jdk.tools.jlink.plugin.PluginException; 43 import jdk.tools.jlink.plugin.Pool; 44 import jdk.tools.jlink.plugin.TransformerPlugin; 45 46 /** 47 * Plugin to generate BoundMethodHandle classes. 48 */ 49 public final class GenerateBMHClassesPlugin implements TransformerPlugin { 50 51 private static final String NAME = "generate-bmh"; 52 53 private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME); 54 55 private static final String BMH = "java/lang/invoke/BoundMethodHandle"; 56 57 private static final Method FACTORY_METHOD; 58 59 List<String> speciesTypes; 60 61 public GenerateBMHClassesPlugin() { 62 } 63 64 @Override 65 public Set<PluginType> getType() { 66 return Collections.singleton(CATEGORY.TRANSFORMER); 67 } 68 69 @Override 70 public String getName() { 71 return NAME; 72 } 73 74 @Override 75 public String getDescription() { 76 return DESCRIPTION; 77 } 78 79 @Override 80 public Set<STATE> getState() { 81 return EnumSet.of(STATE.AUTO_ENABLED, STATE.FUNCTIONAL); 82 } 83 84 @Override 85 public boolean hasArguments() { 86 return true; 87 } 88 89 @Override 90 public String getArgumentsDescription() { 91 return PluginsResourceBundle.getArgument(NAME); 92 } 93 94 @Override 95 public void configure(Map<String, String> config) { 96 String args = config.get(NAME); 97 if (args != null && !args.isEmpty()) { 98 speciesTypes = Arrays.stream(args.split(",")) 99 .map(String::trim) 100 .filter(s -> !s.isEmpty()) 101 .collect(Collectors.toList()); 102 } else { 103 // Default set of Species forms to generate 104 //speciesTypes = Collections.emptyList(); 105 speciesTypes = List.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I", "L7II", 106 "L8", "L9", "L10", "L11", "L11I", "L11II", "L11IIL", 107 "LI", "D", "L3I", "LIL", "LLI", "LLIL", "LILL", "I", "LLILL" 108 ); 109 } 110 } 111 112 @Override 113 public void visit(Pool in, Pool out) { 114 for (Pool.ModuleData data : in.getContent()) { 115 if (("/java.base/" + BMH + "$Factory$SpeciesLookup.class").equals(data.getPath())) { 116 speciesTypes.forEach(types -> generateConcreteClass(types, data, out)); 117 generateSpeciesLookupClass(data, out); 118 } else { 119 out.add(data); 120 } 121 } 122 } 123 124 private void generateSpeciesLookupClass(Pool.ModuleData data, Pool out) { 125 System.out.println("Generating SpeciesLookup"); 126 final String CLASSNAME = "java/lang/invoke/BoundMethodHandle$Factory$SpeciesLookup"; 127 final String GENERATED_SPECIES = "SPECIES"; 128 final String STRING_BUILDER = "java/lang/StringBuilder"; 129 130 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); 131 cw.visit(Opcodes.V1_8, ACC_STATIC + ACC_SUPER, CLASSNAME, 132 "Ljava/lang/Object;Ljava/util/function/Function<Ljava/lang/String;" 133 + "Ljava/lang/Class<+Ljava/lang/invoke/BoundMethodHandle;>;>;", 134 "java/lang/Object", 135 new String[] { "java/util/function/Function" }); 136 137 cw.visitInnerClass("java/lang/invoke/BoundMethodHandle$Factory", 138 "java/lang/invoke/BoundMethodHandle", 139 "Factory", 140 ACC_STATIC); 141 142 cw.visitInnerClass("java/lang/invoke/BoundMethodHandle$Factory$SpeciesLookup", 143 "java/lang/invoke/BoundMethodHandle$Factory", 144 "SpeciesLookup", 145 ACC_STATIC); 146 147 // static HashSet<String> GENERATED_SPECIES 148 cw.visitField(ACC_STATIC, GENERATED_SPECIES, 149 "Ljava/util/HashSet;", "Ljava/util/HashSet<Ljava/lang/String;>;", null) 150 .visitEnd(); 151 152 // static { 153 MethodVisitor mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", 154 null, null); 155 mv.visitCode(); 156 mv.visitTypeInsn(NEW, "java/util/HashSet"); 157 mv.visitInsn(DUP); 158 mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashSet", 159 "<init>", "()V", false); 160 mv.visitVarInsn(ASTORE, 0); 161 162 for (String types : speciesTypes) { 163 mv.visitVarInsn(ALOAD, 0); 164 mv.visitLdcInsn(expandSignature(types)); 165 mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/HashSet", 166 "add", "(Ljava/lang/Object;)Z", false); 167 mv.visitInsn(POP); 168 } 169 170 mv.visitVarInsn(ALOAD, 0); 171 mv.visitFieldInsn(PUTSTATIC, CLASSNAME, GENERATED_SPECIES, "Ljava/util/HashSet;"); 172 mv.visitInsn(RETURN); 173 mv.visitMaxs(0, 0); 174 mv.visitEnd(); 175 176 // Empty constructor 177 mv = cw.visitMethod(0, "<init>", "()V", null, null); 178 mv.visitCode(); 179 mv.visitVarInsn(ALOAD, 0); 180 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 181 mv.visitInsn(RETURN); 182 mv.visitMaxs(0, 0); 183 mv.visitEnd(); 184 185 /* apply method, equivalent of: 186 * 187 * @Override 188 * @SuppressWarnings("unchecked") 189 * public Class<? extends BoundMethodHandle> apply(String types) { 190 * if (GENERATED_SPECIES.contains(types)) { 191 * String cl = "java.lang.invoke.BoundMethodHandle$Species_" 192 * + LambdaForm.shortenSignature(types); 193 * try { 194 * return (Class<? extends BoundMethodHandle>) 195 * Class.forName(cl); 196 * } catch (ClassNotFoundException cnf) { 197 * throw new InternalError(cnf); 198 * } 199 * } 200 * return generateConcreteBMHClass(types); 201 * } 202 */ 203 mv = cw.visitMethod(ACC_PUBLIC, "apply", 204 "(Ljava/lang/String;)Ljava/lang/Class;", 205 "(Ljava/lang/String;)Ljava/lang/Class<+Ljava/lang/invoke/BoundMethodHandle;>;", 206 null); 207 mv.visitCode(); 208 Label start = new Label(); 209 Label end = new Label(); 210 Label handler = new Label(); 211 mv.visitTryCatchBlock(start, end, handler, "java/lang/ClassNotFoundException"); 212 mv.visitFieldInsn(GETSTATIC, CLASSNAME, GENERATED_SPECIES, "Ljava/util/HashSet;"); 213 mv.visitVarInsn(ALOAD, 1); 214 mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/HashSet", "contains", 215 "(Ljava/lang/Object;)Z", false); 216 Label generateClass = new Label(); 217 mv.visitJumpInsn(IFEQ, generateClass); 218 mv.visitTypeInsn(NEW, STRING_BUILDER); 219 mv.visitInsn(DUP); 220 mv.visitMethodInsn(INVOKESPECIAL, STRING_BUILDER, "<init>", "()V", false); 221 mv.visitLdcInsn("java.lang.invoke.BoundMethodHandle$Species_"); 222 mv.visitMethodInsn(INVOKEVIRTUAL, STRING_BUILDER, "append", 223 "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); 224 mv.visitVarInsn(ALOAD, 1); 225 226 mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/LambdaForm", "shortenSignature", 227 "(Ljava/lang/String;)Ljava/lang/String;", false); 228 mv.visitMethodInsn(INVOKEVIRTUAL, STRING_BUILDER, "append", 229 "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); 230 mv.visitMethodInsn(INVOKEVIRTUAL, STRING_BUILDER, "toString", 231 "()Ljava/lang/String;", false); 232 mv.visitVarInsn(ASTORE, 2); 233 mv.visitLabel(start); 234 mv.visitVarInsn(ALOAD, 2); 235 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName", 236 "(Ljava/lang/String;)Ljava/lang/Class;", false); 237 mv.visitLabel(end); 238 mv.visitInsn(ARETURN); 239 mv.visitLabel(handler); 240 mv.visitFrame(Opcodes.F_FULL, 3, 241 new Object[] {"java/lang/invoke/BoundMethodHandle$Factory$SpeciesLookup", 242 "java/lang/String", "java/lang/String"}, 1, 243 new Object[] {"java/lang/ClassNotFoundException"}); 244 mv.visitVarInsn(ASTORE, 3); 245 mv.visitTypeInsn(NEW, "java/lang/InternalError"); 246 mv.visitInsn(DUP); 247 mv.visitVarInsn(ALOAD, 3); 248 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/InternalError", "<init>", 249 "(Ljava/lang/Throwable;)V", false); 250 mv.visitInsn(ATHROW); 251 mv.visitLabel(generateClass); 252 mv.visitFrame(Opcodes.F_CHOP, 1, null, 0, null); 253 254 mv.visitVarInsn(ALOAD, 1); 255 mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/BoundMethodHandle$Factory", 256 "generateConcreteBMHClass", "(Ljava/lang/String;)Ljava/lang/Class;", false); 257 258 mv.visitInsn(ARETURN); 259 mv.visitMaxs(0, 0); 260 mv.visitEnd(); 261 262 // Synthetic bridge method to make apply non-abstract 263 mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, 264 "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", null, null); 265 mv.visitCode(); 266 mv.visitVarInsn(ALOAD, 0); 267 mv.visitVarInsn(ALOAD, 1); 268 mv.visitTypeInsn(CHECKCAST, "java/lang/String"); 269 mv.visitMethodInsn(INVOKEVIRTUAL, 270 "java/lang/invoke/BoundMethodHandle$Factory$SpeciesLookup", 271 "apply", "(Ljava/lang/String;)Ljava/lang/Class;", false); 272 mv.visitInsn(ARETURN); 273 mv.visitMaxs(0, 0); 274 mv.visitEnd(); 275 cw.visitEnd(); 276 277 System.out.println("Generated SpeciesLookup"); 278 byte[] bytes = cw.toByteArray(); 279 Pool.ModuleData ndata = new Pool.ModuleData(data.getModule(), 280 "/java.base/" + BMH + "$Factory$SpeciesLookup.class", 281 Pool.ModuleDataType.CLASS_OR_RESOURCE, 282 new ByteArrayInputStream(bytes), bytes.length); 283 out.add(ndata); 284 } 285 286 private void generateConcreteClass(String shortTypes, Pool.ModuleData data, Pool out) { 287 try { 288 String types = expandSignature(shortTypes); 289 290 // Generate class 291 byte[] bytes = (byte[])FACTORY_METHOD.invoke(null, 292 BMH + "$Species_" + shortTypes, // class name 293 "Species_" + shortTypes, // source name 294 types); 295 296 // Add class to pool 297 Pool.ModuleData ndata = new Pool.ModuleData(data.getModule(), 298 "/java.base/" + BMH + "$Species_" + shortTypes + ".class", 299 Pool.ModuleDataType.CLASS_OR_RESOURCE, 300 new ByteArrayInputStream(bytes), bytes.length); 301 out.add(ndata); 302 } catch (Exception ex) { 303 throw new PluginException(ex); 304 } 305 } 306 307 static { 308 try { 309 Class<?> BMHFactory = Class.forName("java.lang.invoke.BoundMethodHandle$Factory"); 310 Method genClassMethod = BMHFactory.getDeclaredMethod("generateConcreteBMHClassBytes", 311 String.class, String.class, String.class); 312 genClassMethod.setAccessible(true); 313 FACTORY_METHOD = genClassMethod; 314 } catch (Exception e) { 315 throw new PluginException(e); 316 } 317 } 318 319 // Convert LL -> LL, L3 -> LLL 320 private static String expandSignature(String signature) { 321 StringBuilder sb = new StringBuilder(); 322 char last = 'X'; 323 int count = 0; 324 for (int i = 0; i < signature.length(); i++) { 325 char c = signature.charAt(i); 326 if (c >= '0' && c <= '9') { 327 count *= 10; 328 count += (c - '0'); 329 } else { 330 for (int j = 1; j < count; j++) { 331 sb.append(last); 332 } 333 sb.append(c); 334 last = c; 335 count = 0; 336 } 337 } 338 for (int j = 1; j < count; j++) { 339 sb.append(last); 340 } 341 return sb.toString(); 342 } 343 344 }