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 }