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.util.Iterator;
  28 import java.util.List;
  29 import java.util.Map;
  30 import java.util.Objects;
  31 import java.util.stream.Collectors;
  32 import jdk.tools.jlink.plugin.ModulePool;
  33 import jdk.tools.jlink.plugin.Plugin.Category;
  34 import jdk.internal.org.objectweb.asm.ClassReader;
  35 import static jdk.internal.org.objectweb.asm.ClassReader.*;
  36 import jdk.internal.org.objectweb.asm.ClassWriter;
  37 import jdk.internal.org.objectweb.asm.Opcodes;
  38 import jdk.internal.org.objectweb.asm.Type;
  39 import jdk.internal.org.objectweb.asm.tree.AbstractInsnNode;
  40 import jdk.internal.org.objectweb.asm.tree.ClassNode;
  41 import jdk.internal.org.objectweb.asm.tree.InsnList;
  42 import jdk.internal.org.objectweb.asm.tree.LabelNode;
  43 import jdk.internal.org.objectweb.asm.tree.LdcInsnNode;
  44 import jdk.internal.org.objectweb.asm.tree.LineNumberNode;
  45 import jdk.internal.org.objectweb.asm.tree.MethodInsnNode;
  46 import jdk.internal.org.objectweb.asm.tree.MethodNode;
  47 import jdk.tools.jlink.plugin.ModuleEntry;
  48 import jdk.tools.jlink.plugin.Plugin;
  49 
  50 public final class ClassForNamePlugin implements Plugin {
  51     public static final String NAME = "class-for-name";
  52 
  53     private static String binaryClassName(String path) {
  54         return path.substring(path.indexOf('/', 1) + 1,
  55                               path.length() - ".class".length());
  56     }
  57 
  58     private static int getAccess(ModuleEntry resource) {
  59         ClassReader cr = new ClassReader(resource.getBytes());
  60 
  61         return cr.getAccess();
  62     }
  63 
  64     private static String getPackage(String binaryName) {
  65         int index = binaryName.lastIndexOf("/");
  66 
  67         return index == -1 ? "" : binaryName.substring(0, index);
  68     }
  69 
  70     private ModuleEntry transform(ModuleEntry resource, Map<String, ModuleEntry> classes) {
  71         byte[] inBytes = resource.getBytes();
  72         ClassReader cr = new ClassReader(inBytes);
  73         ClassNode cn = new ClassNode();
  74         cr.accept(cn, EXPAND_FRAMES);
  75         List<MethodNode> ms = cn.methods;
  76         boolean modified = false;
  77         LdcInsnNode ldc = null;
  78 
  79         String thisPackage = getPackage(binaryClassName(resource.getPath()));
  80 
  81         for (MethodNode mn : ms) {
  82             InsnList il = mn.instructions;
  83             Iterator<AbstractInsnNode> it = il.iterator();
  84 
  85             while (it.hasNext()) {
  86                 AbstractInsnNode insn = it.next();
  87 
  88                 if (insn instanceof LdcInsnNode) {
  89                     ldc = (LdcInsnNode)insn;
  90                 } else if (insn instanceof MethodInsnNode && ldc != null) {
  91                     MethodInsnNode min = (MethodInsnNode)insn;
  92 
  93                     if (min.getOpcode() == Opcodes.INVOKESTATIC &&
  94                         min.name.equals("forName") &&
  95                         min.owner.equals("java/lang/Class") &&
  96                         min.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
  97                         String ldcClassName = ldc.cst.toString();
  98                         String thatClassName = ldcClassName.replaceAll("\\.", "/");
  99                         ModuleEntry thatClass = classes.get(thatClassName);
 100 
 101                         if (thatClass != null) {
 102                             int thatAccess = getAccess(thatClass);
 103                             String thatPackage = getPackage(thatClassName);
 104 
 105                             if ((thatAccess & Opcodes.ACC_PRIVATE) != Opcodes.ACC_PRIVATE &&
 106                                 ((thatAccess & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC ||
 107                                   thisPackage.equals(thatPackage))) {
 108                                 Type type = Type.getObjectType(thatClassName);
 109                                 il.remove(ldc);
 110                                 il.set(min, new LdcInsnNode(type));
 111                                 modified = true;
 112                             }
 113                         }
 114                     }
 115 
 116                     ldc = null;
 117                 } else if (!(insn instanceof LabelNode) &&
 118                            !(insn instanceof LineNumberNode)) {
 119                     ldc = null;
 120                 }
 121 
 122             }
 123         }
 124 
 125         if (modified) {
 126             ClassWriter cw = new ClassWriter(cr, 0);
 127             cn.accept(cw);
 128             byte[] outBytes = cw.toByteArray();
 129 
 130             return resource.create(outBytes);
 131         }
 132 
 133         return resource;
 134     }
 135 
 136     @Override
 137     public String getName() {
 138         return NAME;
 139     }
 140 
 141     @Override
 142     public void visit(ModulePool in, ModulePool out) {
 143         Objects.requireNonNull(in);
 144         Objects.requireNonNull(out);
 145         Map<String, ModuleEntry> classes = in.entries()
 146             .filter(resource -> resource != null &&
 147                     resource.getPath().endsWith(".class") &&
 148                     !resource.getPath().endsWith("/module-info.class"))
 149             .collect(Collectors.toMap(resource -> binaryClassName(resource.getPath()),
 150                                       resource -> resource));
 151         in.entries()
 152             .filter(resource -> resource != null)
 153             .forEach(resource -> {
 154                 String path = resource.getPath();
 155 
 156                 if (path.endsWith(".class") && !path.endsWith("/module-info.class")) {
 157                     out.add(transform(resource, classes));
 158                 } else {
 159                     out.add(resource);
 160                 }
 161             });
 162     }
 163 
 164     @Override
 165     public Category getType() {
 166         return Category.TRANSFORMER;
 167     }
 168 
 169     @Override
 170     public boolean hasArguments() {
 171         return false;
 172     }
 173 
 174     @Override
 175     public String getDescription() {
 176         return PluginsResourceBundle.getDescription(NAME);
 177     }
 178 
 179     @Override
 180     public String getArgumentsDescription() {
 181        return PluginsResourceBundle.getArgument(NAME);
 182     }
 183 
 184     @Override
 185     public void configure(Map<String, String> config) {
 186 
 187     }
 188 }