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 }