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