1 /*
   2  * Copyright (c) 2015, 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.FileOutputStream;
  28 import java.io.IOException;
  29 import java.io.OutputStream;
  30 import java.io.UncheckedIOException;
  31 import java.nio.charset.StandardCharsets;
  32 import java.util.ArrayList;
  33 import java.util.List;
  34 import java.util.Map;
  35 import java.util.Objects;
  36 import java.util.Set;
  37 import java.util.function.Consumer;
  38 import jdk.internal.org.objectweb.asm.ClassReader;
  39 import jdk.internal.org.objectweb.asm.ClassWriter;
  40 import jdk.internal.org.objectweb.asm.Opcodes;
  41 import jdk.tools.jlink.internal.plugins.asm.AsmPools;
  42 import jdk.tools.jlink.internal.plugins.asm.AsmPlugin;
  43 import jdk.internal.org.objectweb.asm.tree.ClassNode;
  44 import jdk.internal.org.objectweb.asm.tree.MethodNode;
  45 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
  46 import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
  47 import jdk.tools.jlink.internal.plugins.optim.ForNameFolding;
  48 import jdk.tools.jlink.internal.plugins.optim.ReflectionOptimizer.TypeResolver;
  49 import jdk.tools.jlink.plugin.PluginException;
  50 
  51 /**
  52  *
  53  * Optimize Classes following various strategies. Strategies are implementation
  54  * of <code>ClassOptimizer</code> and <code>MethodOptimizer</code>.
  55  */
  56 public final class OptimizationPlugin extends AsmPlugin {
  57 
  58     public static final String NAME = "class-optim";
  59     public static final String LOG  = "log";
  60     public static final String ALL = "all";
  61     public static final String FORNAME_REMOVAL = "forName-folding";
  62 
  63     /**
  64      * Default resolver. A resolver that retrieve types that are in an
  65      * accessible package, are public or are located in the same package as the
  66      * caller.
  67      */
  68     private static final class DefaultTypeResolver implements TypeResolver {
  69 
  70         private final Set<String> packages;
  71         private final AsmPools pools;
  72 
  73         DefaultTypeResolver(AsmPools pools, AsmModulePool modulePool) {
  74             Objects.requireNonNull(pools);
  75             Objects.requireNonNull(modulePool);
  76             this.pools = pools;
  77             packages = pools.getGlobalPool().getAccessiblePackages(modulePool.getModuleName());
  78         }
  79 
  80         @Override
  81         public ClassReader resolve(ClassNode cn, MethodNode mn, String type) {
  82             int classIndex = cn.name.lastIndexOf("/");
  83             String callerPkg = classIndex == -1 ? ""
  84                     : cn.name.substring(0, classIndex);
  85             int typeClassIndex = type.lastIndexOf("/");
  86             String pkg = typeClassIndex == - 1 ? ""
  87                     : type.substring(0, typeClassIndex);
  88             ClassReader reader = null;
  89             if (packages.contains(pkg) || pkg.equals(callerPkg)) {
  90                 ClassReader r = pools.getGlobalPool().getClassReader(type);
  91                 if (r != null) {
  92                     // if not private
  93                     if ((r.getAccess() & Opcodes.ACC_PRIVATE)
  94                             != Opcodes.ACC_PRIVATE) {
  95                         // public
  96                         if (((r.getAccess() & Opcodes.ACC_PUBLIC)
  97                                 == Opcodes.ACC_PUBLIC)) {
  98                             reader = r;
  99                         } else if (pkg.equals(callerPkg)) {
 100                             reader = r;
 101                         }
 102                     }
 103                 }
 104             }
 105             return reader;
 106         }
 107     }
 108 
 109     public interface Optimizer {
 110 
 111         void close() throws IOException;
 112     }
 113 
 114     public interface ClassOptimizer extends Optimizer {
 115 
 116         boolean optimize(Consumer<String> logger, AsmPools pools,
 117                 AsmModulePool modulePool,
 118                 ClassNode cn) throws Exception;
 119     }
 120 
 121     public interface MethodOptimizer extends Optimizer {
 122 
 123         boolean optimize(Consumer<String> logger, AsmPools pools,
 124                 AsmModulePool modulePool,
 125                 ClassNode cn, MethodNode m, TypeResolver resolver) throws Exception;
 126     }
 127 
 128     private List<Optimizer> optimizers = new ArrayList<>();
 129 
 130     private OutputStream stream;
 131     private int numMethods;
 132 
 133     private void log(String content) {
 134         if (stream != null) {
 135             try {
 136                 content = content + "\n";
 137                 stream.write(content.getBytes(StandardCharsets.UTF_8));
 138             } catch (IOException ex) {
 139                 System.err.println(ex);
 140             }
 141         }
 142     }
 143 
 144     private void close() throws IOException {
 145         log("Num analyzed methods " + numMethods);
 146 
 147         for (Optimizer optimizer : optimizers) {
 148             try {
 149                 optimizer.close();
 150             } catch (IOException ex) {
 151                 System.err.println("Error closing optimizer " + ex);
 152             }
 153         }
 154         if (stream != null) {
 155             stream.close();
 156         }
 157     }
 158 
 159     @Override
 160     public String getName() {
 161         return NAME;
 162     }
 163 
 164     @Override
 165     public void visit(AsmPools pools) {
 166         try {
 167             for (AsmModulePool p : pools.getModulePools()) {
 168                 DefaultTypeResolver resolver = new DefaultTypeResolver(pools, p);
 169                 p.visitClassReaders((reader) -> {
 170                     ClassWriter w = null;
 171                     try {
 172                         w = optimize(pools, p, reader, resolver);
 173                     } catch (IOException ex) {
 174                         throw new PluginException("Problem optimizing "
 175                                 + reader.getClassName(), ex);
 176                     }
 177                     return w;
 178                 });
 179             }
 180         } finally {
 181             try {
 182                 close();
 183             } catch (IOException ex) {
 184                 throw new UncheckedIOException(ex);
 185             }
 186         }
 187     }
 188 
 189     private ClassWriter optimize(AsmPools pools, AsmModulePool modulePool,
 190             ClassReader reader, TypeResolver resolver)
 191             throws IOException {
 192         ClassNode cn = new ClassNode();
 193         ClassWriter writer = null;
 194         if ((reader.getAccess() & Opcodes.ACC_INTERFACE) == 0) {
 195             reader.accept(cn, ClassReader.EXPAND_FRAMES);
 196             boolean optimized = false;
 197             for (Optimizer optimizer : optimizers) {
 198                 if (optimizer instanceof ClassOptimizer) {
 199                     try {
 200                         boolean optim = ((ClassOptimizer) optimizer).
 201                                 optimize(this::log, pools, modulePool, cn);
 202                         if (optim) {
 203                             optimized = true;
 204                         }
 205                     } catch (Throwable ex) {
 206                         throw new PluginException("Exception optimizing "
 207                                 + reader.getClassName(), ex);
 208                     }
 209                 } else {
 210                     MethodOptimizer moptimizer = (MethodOptimizer) optimizer;
 211                     for (MethodNode m : cn.methods) {
 212                         if ((m.access & Opcodes.ACC_ABSTRACT) == 0
 213                                 && (m.access & Opcodes.ACC_NATIVE) == 0) {
 214                             numMethods += 1;
 215                             try {
 216                                 boolean optim = moptimizer.
 217                                         optimize(this::log, pools, modulePool, cn,
 218                                                 m, resolver);
 219                                 if (optim) {
 220                                     optimized = true;
 221                                 }
 222                             } catch (Throwable ex) {
 223                                 throw new PluginException("Exception optimizing "
 224                                         + reader.getClassName() + "." + m.name, ex);
 225                             }
 226 
 227                         }
 228                     }
 229                 }
 230             }
 231 
 232             if (optimized) {
 233                 writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
 234                 try {
 235                     // add a validation layer in between to check for class vallidity
 236                     CheckClassAdapter ca = new CheckClassAdapter(writer);
 237                     cn.accept(ca);
 238                 } catch (Exception ex) {
 239                     throw new PluginException("Exception optimizing class " + cn.name, ex);
 240                 }
 241             }
 242         }
 243         return writer;
 244     }
 245 
 246     @Override
 247     public String getDescription() {
 248         return PluginsResourceBundle.getDescription(NAME);
 249     }
 250 
 251     @Override
 252     public boolean hasArguments() {
 253         return true;
 254     }
 255 
 256     @Override
 257     public String getArgumentsDescription() {
 258        return PluginsResourceBundle.getArgument(NAME);
 259     }
 260 
 261     @Override
 262     public void configure(Map<String, String> config) {
 263         String strategies = config.get(NAME);
 264         String[] arr = strategies.split(",");
 265         for (String s : arr) {
 266             if (s.equals(ALL)) {
 267                 optimizers.clear();
 268                 optimizers.add(new ForNameFolding());
 269                 break;
 270             } else if (s.equals(FORNAME_REMOVAL)) {
 271                 optimizers.add(new ForNameFolding());
 272             } else {
 273                 throw new IllegalArgumentException("Unknown optimization: " + s);
 274             }
 275         }
 276         String f = config.get(LOG);
 277         if (f != null) {
 278             try {
 279                 stream = new FileOutputStream(f);
 280             } catch (IOException ex) {
 281                 throw new UncheckedIOException(ex);
 282             }
 283         }
 284     }
 285 }