001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.weaver.normalizer; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.lang.annotation.ElementType; 023import java.lang.annotation.Target; 024import java.nio.charset.Charset; 025import java.security.MessageDigest; 026import java.security.NoSuchAlgorithmException; 027import java.text.MessageFormat; 028import java.util.HashMap; 029import java.util.LinkedHashMap; 030import java.util.LinkedHashSet; 031import java.util.Map; 032import java.util.Set; 033 034import javax.activation.DataSource; 035 036import org.apache.commons.io.IOUtils; 037import org.apache.commons.lang3.ArrayUtils; 038import org.apache.commons.lang3.CharEncoding; 039import org.apache.commons.lang3.Conversion; 040import org.apache.commons.lang3.Validate; 041import org.apache.commons.lang3.mutable.MutableBoolean; 042import org.apache.commons.lang3.tuple.ImmutablePair; 043import org.apache.commons.lang3.tuple.MutablePair; 044import org.apache.commons.lang3.tuple.Pair; 045import org.apache.commons.weaver.model.ScanRequest; 046import org.apache.commons.weaver.model.ScanResult; 047import org.apache.commons.weaver.model.Scanner; 048import org.apache.commons.weaver.model.WeavableClass; 049import org.apache.commons.weaver.model.WeaveEnvironment; 050import org.apache.commons.weaver.spi.Weaver; 051import org.objectweb.asm.AnnotationVisitor; 052import org.objectweb.asm.ClassReader; 053import org.objectweb.asm.ClassVisitor; 054import org.objectweb.asm.ClassWriter; 055import org.objectweb.asm.MethodVisitor; 056import org.objectweb.asm.Opcodes; 057import org.objectweb.asm.Type; 058import org.objectweb.asm.commons.GeneratorAdapter; 059import org.objectweb.asm.commons.Method; 060import org.objectweb.asm.commons.Remapper; 061import org.objectweb.asm.commons.RemappingClassAdapter; 062import org.objectweb.asm.commons.SimpleRemapper; 063 064/** 065 * Handles the work of "normalizing" anonymous class definitions. 066 */ 067public class Normalizer { 068 private static final String INIT = "<init>"; 069 070 private static final Type OBJECT_TYPE = Type.getType(Object.class); 071 072 private static final class Inspector extends ClassVisitor { 073 private final class InspectConstructor extends MethodVisitor { 074 private InspectConstructor() { 075 super(Opcodes.ASM5); 076 } 077 078 @Override 079 public void visitMethodInsn(final int opcode, final String owner, final String name, 080 final String desc, final boolean itf) { 081 if (INIT.equals(name) && owner.equals(superName)) { 082 key.setRight(desc); 083 } else { 084 valid.setValue(false); 085 } 086 } 087 088 @Override 089 public void visitFieldInsn(final int opcode, final String owner, final String name, 090 final String desc) { 091 if ("this$0".equals(name) && opcode == Opcodes.PUTFIELD) { 092 mustRewriteConstructor.setValue(true); 093 return; 094 } 095 valid.setValue(false); 096 } 097 } 098 099 private final MutablePair<String, String> key = new MutablePair<String, String>(); 100 private final MutableBoolean ignore = new MutableBoolean(false); 101 private final MutableBoolean valid = new MutableBoolean(true); 102 private final MutableBoolean mustRewriteConstructor = new MutableBoolean(false); 103 104 private String superName; 105 106 private Inspector() { 107 super(Opcodes.ASM5); 108 } 109 110 @Override 111 public void visit(final int version, final int access, final String name, final String signature, 112 final String superName, final String[] interfaces) { 113 super.visit(version, access, name, signature, superName, interfaces); 114 this.superName = superName; 115 final String left; 116 if (signature != null) { 117 left = signature; 118 } else if (ArrayUtils.getLength(interfaces) == 1) { 119 left = interfaces[0]; 120 } else { 121 left = superName; 122 } 123 key.setLeft(left); 124 } 125 126 @Override 127 public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { 128 if (Type.getType(Marker.class).getDescriptor().equals(desc)) { 129 ignore.setValue(true); 130 } 131 return null; 132 } 133 134 @Override 135 public MethodVisitor visitMethod(final int access, final String name, final String desc, 136 final String signature, final String[] exceptions) { 137 return INIT.equals(name) ? new InspectConstructor() : null; 138 } 139 140 Pair<String, String> key() { 141 return ImmutablePair.of(key.getLeft(), key.getRight()); 142 } 143 144 boolean ignore() { 145 return ignore.booleanValue(); 146 } 147 148 boolean valid() { 149 return valid.booleanValue(); 150 } 151 152 boolean mustRewriteConstructor() { 153 return mustRewriteConstructor.booleanValue(); 154 } 155 } 156 157 private static final class Remap extends RemappingClassAdapter { 158 private final class RewriteConstructor extends MethodVisitor { 159 private RewriteConstructor(final MethodVisitor wrapped) { 160 super(Opcodes.ASM5, wrapped); 161 } 162 163 @Override 164 public void visitMethodInsn(final int opcode, final String owner, final String name, 165 final String desc, final boolean itf) { 166 String useDescriptor = desc; 167 final ClassWrapper wrapper = wrappers.get(owner); 168 if (wrapper != null && wrapper.mustRewriteConstructor) { 169 // simply replace first argument type with OBJECT_TYPE: 170 final Type[] args = Type.getArgumentTypes(desc); 171 args[0] = OBJECT_TYPE; 172 useDescriptor = new Method(INIT, Type.VOID_TYPE, args).getDescriptor(); 173 } 174 super.visitMethodInsn(opcode, owner, name, useDescriptor, itf); 175 } 176 } 177 178 private final Map<String, String> classMap; 179 private final Map<String, ClassWrapper> wrappers; 180 181 private Remap(final ClassVisitor wrapped, final Remapper remapper, final Map<String, String> classMap, 182 final Map<String, ClassWrapper> wrappers) { 183 super(wrapped, remapper); 184 this.classMap = classMap; 185 this.wrappers = wrappers; 186 } 187 188 @Override 189 public void visitInnerClass(final String name, final String outerName, final String innerName, 190 final int access) { 191 if (!classMap.containsKey(name)) { 192 super.visitInnerClass(name, outerName, innerName, access); 193 } 194 } 195 196 @Override 197 public MethodVisitor visitMethod(final int access, final String name, final String desc, 198 final String signature, final String[] exceptions) { 199 final MethodVisitor toWrap = super.visitMethod(access, name, desc, signature, exceptions); 200 return INIT.equals(name) ? new RewriteConstructor(toWrap) : toWrap; 201 } 202 } 203 204 /** 205 * Marker annotation. 206 */ 207 @Target(ElementType.TYPE) 208 private @interface Marker { 209 } 210 211 private static class ClassWrapper { 212 final Class<?> wrapped; 213 final boolean mustRewriteConstructor; 214 215 ClassWrapper(final Class<?> wrapped, final boolean mustRewriteConstructor) { 216 this.wrapped = wrapped; 217 this.mustRewriteConstructor = mustRewriteConstructor; 218 } 219 } 220 221 /** 222 * Necessary to resolve supertypes against WeaveEnvironment ClassLoader. 223 */ 224 private final class CustomClassWriter extends ClassWriter { 225 CustomClassWriter(final int flags) { 226 super(flags); 227 } 228 229 CustomClassWriter(final ClassReader classReader, final int flags) { 230 super(classReader, flags); 231 } 232 233 @Override 234 protected String getCommonSuperClass(final String type1, final String type2) { 235 Class<?> class1; 236 Class<?> class2; 237 try { 238 class1 = Class.forName(type1.replace('/', '.'), false, env.classLoader); 239 class2 = Class.forName(type2.replace('/', '.'), false, env.classLoader); 240 } catch (Exception e) { 241 throw new RuntimeException(e.toString()); 242 } 243 if (class1.isAssignableFrom(class2)) { 244 return type1; 245 } 246 if (class2.isAssignableFrom(class1)) { 247 return type2; 248 } 249 if (class1.isInterface() || class2.isInterface()) { 250 return "java/lang/Object"; 251 } 252 do { 253 class1 = class1.getSuperclass(); 254 } while (!class1.isAssignableFrom(class2)); 255 return class1.getName().replace('.', '/'); 256 } 257 } 258 259 private class WriteClass extends ClassVisitor { 260 private String className; 261 262 WriteClass() { 263 super(Opcodes.ASM5, new CustomClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS)); 264 } 265 266 WriteClass(final ClassReader reader) { 267 super(Opcodes.ASM5, new CustomClassWriter(reader, 0)); 268 } 269 270 @Override 271 public void visit(final int version, final int access, final String name, final String signature, 272 final String superName, final String[] intrfces) { 273 super.visit(version, access, name, signature, superName, intrfces); 274 className = name; 275 } 276 277 @Override 278 public void visitEnd() { 279 super.visitEnd(); 280 final byte[] bytecode = ((ClassWriter) cv).toByteArray(); 281 282 final DataSource classfile = env.getClassfile(className); 283 env.debug("Writing class %s to %s", className, classfile.getName()); 284 OutputStream outputStream = null; 285 try { 286 outputStream = classfile.getOutputStream(); 287 IOUtils.write(bytecode, outputStream); 288 } catch (final IOException e) { 289 throw new RuntimeException(e); 290 } finally { 291 IOUtils.closeQuietly(outputStream); 292 } 293 } 294 } 295 296 private enum IneligibilityReason { 297 NOT_ANONYMOUS, TOO_MANY_CONSTRUCTORS, IMPLEMENTS_METHODS, TOO_BUSY_CONSTRUCTOR; 298 } 299 300 /** 301 * Configuration prefix for this {@link Weaver}. 302 */ 303 public static final String CONFIG_WEAVER = "normalizer."; 304 305 /** 306 * Property name referencing a comma-delimited list of types whose subclasses/implementations should be normalized, 307 * e.g. {@code javax.enterprise.util.TypeLiteral}. 308 */ 309 public static final String CONFIG_SUPER_TYPES = CONFIG_WEAVER + "superTypes"; 310 311 /** 312 * Property name referencing a package name to which merged types should be added. 313 */ 314 public static final String CONFIG_TARGET_PACKAGE = CONFIG_WEAVER + "targetPackage"; 315 316 private static final Charset UTF8 = Charset.forName(CharEncoding.UTF_8); 317 318 private final WeaveEnvironment env; 319 320 private final Set<Class<?>> normalizeTypes; 321 private final String targetPackage; 322 323 /** 324 * Create a new {@link Normalizer} instance. 325 * @param env {@link WeaveEnvironment} 326 */ 327 public Normalizer(final WeaveEnvironment env) { 328 this.env = env; 329 330 this.targetPackage = 331 Utils.validatePackageName(Validate.notBlank(env.config.getProperty(CONFIG_TARGET_PACKAGE), 332 "missing target package name")); 333 this.normalizeTypes = 334 Utils.parseTypes( 335 Validate.notEmpty(env.config.getProperty(CONFIG_SUPER_TYPES), "no types specified for normalization"), 336 env.classLoader); 337 } 338 339 /** 340 * Normalize the classes found using the specified {@link Scanner}. 341 * @param scanner to scan with 342 * @return whether any work was done 343 */ 344 public boolean normalize(final Scanner scanner) { 345 boolean result = false; 346 for (final Class<?> supertype : normalizeTypes) { 347 final Set<Class<?>> subtypes = getBroadlyEligibleSubclasses(supertype, scanner); 348 try { 349 final Map<Pair<String, String>, Set<ClassWrapper>> segregatedSubtypes = segregate(subtypes); 350 for (final Map.Entry<Pair<String, String>, Set<ClassWrapper>> entry : segregatedSubtypes.entrySet()) { 351 final Set<ClassWrapper> likeTypes = entry.getValue(); 352 if (likeTypes.size() > 1) { 353 result = true; 354 rewrite(entry.getKey(), likeTypes); 355 } 356 } 357 } catch (final RuntimeException e) { 358 throw e; 359 } catch (final Exception e) { 360 throw new RuntimeException(e); 361 } 362 } 363 return result; 364 } 365 366 /** 367 * Map a set of classes by their enclosing class. 368 * @param sort values 369 * @return {@link Map} of enclosing classname to {@link Map} of internal name to {@link ClassWrapper} 370 */ 371 private Map<String, Map<String, ClassWrapper>> byEnclosingClass(final Set<ClassWrapper> sort) { 372 final Map<String, Map<String, ClassWrapper>> result = new HashMap<String, Map<String, ClassWrapper>>(); 373 for (final ClassWrapper wrapper : sort) { 374 final String outer = wrapper.wrapped.getEnclosingClass().getName(); 375 Map<String, ClassWrapper> map = result.get(outer); 376 if (map == null) { 377 map = new LinkedHashMap<String, Normalizer.ClassWrapper>(); 378 result.put(outer, map); 379 } 380 map.put(wrapper.wrapped.getName().replace('.', '/'), wrapper); 381 } 382 return result; 383 } 384 385 /** 386 * Rewrite classes as indicated by one entry of {@link #segregate(Iterable)}. 387 * @param key {@link String} {@link Pair} indicating supertype and constructor signature 388 * @param toMerge matching classes 389 * @throws IOException on I/O error 390 * @throws ClassNotFoundException if class not found 391 */ 392 private void rewrite(final Pair<String, String> key, final Set<ClassWrapper> toMerge) throws IOException, 393 ClassNotFoundException { 394 final String target = copy(key, toMerge.iterator().next()); 395 env.info("Merging %s identical %s implementations with constructor %s to type %s", toMerge.size(), 396 key.getLeft(), key.getRight(), target); 397 398 final Map<String, Map<String, ClassWrapper>> byEnclosingClass = byEnclosingClass(toMerge); 399 for (final Map.Entry<String, Map<String, ClassWrapper>> entry : byEnclosingClass.entrySet()) { 400 final String outer = entry.getKey(); 401 env.debug("Normalizing %s inner classes of %s", entry.getValue().size(), outer); 402 final Map<String, String> classMap = new HashMap<String, String>(); 403 for (final String merged : entry.getValue().keySet()) { 404 classMap.put(merged, target); 405 } 406 final Remapper remapper = new SimpleRemapper(classMap); 407 408 InputStream enclosingBytecode = null; 409 try { 410 enclosingBytecode = env.getClassfile(outer).getInputStream(); 411 final ClassReader reader = new ClassReader(enclosingBytecode); 412 reader.accept(new Remap(new WriteClass(reader), remapper, classMap, entry.getValue()), 0); 413 } finally { 414 IOUtils.closeQuietly(enclosingBytecode); 415 } 416 for (final String merged : entry.getValue().keySet()) { 417 if (env.deleteClassfile(merged)) { 418 env.debug("Deleted class %s", merged); 419 } else { 420 env.warn("Unable to delete class %s", merged); 421 } 422 } 423 } 424 } 425 426 /** 427 * <p>Find subclasses/implementors of {code supertype} that: 428 * <ul> 429 * <li>are anonymous</li> 430 * <li>declare a single constructor (probably redundant in the case of an anonymous class)</li> 431 * <li>do not implement any methods</li> 432 * </ul> 433 * </p><p> 434 * Considered "broadly" eligible because the instructions in the implemented constructor may remove the class from 435 * consideration later on. 436 * </p> 437 * @param supertype whose subtypes are sought 438 * @param scanner to use 439 * @return {@link Set} of {@link Class} 440 * @see #segregate(Iterable) 441 */ 442 private Set<Class<?>> getBroadlyEligibleSubclasses(final Class<?> supertype, final Scanner scanner) { 443 final ScanResult scanResult = scanner.scan(new ScanRequest().addSupertypes(supertype)); 444 final Set<Class<?>> result = new LinkedHashSet<Class<?>>(); 445 for (final WeavableClass<?> cls : scanResult.getClasses()) { 446 final Class<?> subtype = cls.getTarget(); 447 final IneligibilityReason reason; 448 if (!subtype.isAnonymousClass()) { 449 reason = IneligibilityReason.NOT_ANONYMOUS; 450 } else if (subtype.getDeclaredConstructors().length != 1) { 451 reason = IneligibilityReason.TOO_MANY_CONSTRUCTORS; 452 } else if (subtype.getDeclaredMethods().length > 0) { 453 reason = IneligibilityReason.IMPLEMENTS_METHODS; 454 } else { 455 result.add(subtype); 456 continue; 457 } 458 env.debug("Removed %s from consideration due to %s", subtype, reason); 459 } 460 return result; 461 } 462 463 /** 464 * <p>Segregate a number of classes (presumed subclasses/implementors of a 465 * common supertype/interface). The keys of the map consist of the important 466 * parts for identifying similar anonymous types: the "signature" and the 467 * invoked superclass constructor. For our purposes, the signature consists 468 * of the first applicable item of: 469 * <ol> 470 * <li>The generic signature of the class</li> 471 * <li>The sole implemented interface</li> 472 * <li>The superclass</li> 473 * </ol> 474 * </p><p> 475 * The class will be considered ineligible if its constructor is too "busy" as its side effects cannot be 476 * anticipated; the normalizer will err on the side of caution. 477 * </p><p> 478 * Further, we will here avail ourselves of the opportunity to discard any types we have already normalized. 479 * </p> 480 * @param subtypes 481 * @return Map of Pair<String, String> to Set of Classes 482 * @throws IOException 483 */ 484 private Map<Pair<String, String>, Set<ClassWrapper>> segregate(final Iterable<Class<?>> subtypes) 485 throws IOException { 486 final Map<Pair<String, String>, Set<ClassWrapper>> classMap = 487 new LinkedHashMap<Pair<String, String>, Set<ClassWrapper>>(); 488 for (final Class<?> subtype : subtypes) { 489 final Inspector inspector = new Inspector(); 490 InputStream bytecode = null; 491 try { 492 bytecode = env.getClassfile(subtype).getInputStream(); 493 new ClassReader(bytecode).accept(inspector, 0); 494 } finally { 495 IOUtils.closeQuietly(bytecode); 496 } 497 if (inspector.ignore()) { 498 continue; 499 } 500 if (inspector.valid()) { 501 final Pair<String, String> key = inspector.key(); 502 Set<ClassWrapper> set = classMap.get(key); 503 if (set == null) { 504 set = new LinkedHashSet<ClassWrapper>(); 505 classMap.put(key, set); 506 } 507 set.add(new ClassWrapper(subtype, inspector.mustRewriteConstructor())); 508 } else { 509 env.debug("%s is ineligible for normalization due to %s", subtype, 510 IneligibilityReason.TOO_BUSY_CONSTRUCTOR); 511 } 512 } 513 return classMap; 514 } 515 516 /** 517 * Create the normalized version of a given class in the configured target package. The {@link Normalizer} will 518 * gladly do so in a package from which the normalized class will not actually be able to reference any types upon 519 * which it relies; in such a situation you must specify the target package as the package of the supertype. 520 * @param key used to generate the normalized classname. 521 * @param classWrapper 522 * @return the generated classname. 523 * @throws IOException 524 * @throws ClassNotFoundException 525 */ 526 private String copy(final Pair<String, String> key, final ClassWrapper classWrapper) throws IOException, 527 ClassNotFoundException { 528 env.debug("Copying %s to %s", key, targetPackage); 529 final MessageDigest md5; 530 try { 531 md5 = MessageDigest.getInstance("MD5"); 532 } catch (final NoSuchAlgorithmException e) { 533 throw new RuntimeException(e); 534 } 535 md5.update(key.getLeft().getBytes(UTF8)); 536 md5.update(key.getRight().getBytes(UTF8)); 537 538 final long digest = Conversion.byteArrayToLong(md5.digest(), 0, 0L, 0, Long.SIZE / Byte.SIZE); 539 540 final String result = MessageFormat.format("{0}/$normalized{1,number,0;_0}", targetPackage, digest); 541 542 env.debug("Copying class %s to %s", classWrapper.wrapped.getName(), result); 543 544 InputStream bytecode = null; 545 546 try { 547 bytecode = env.getClassfile(classWrapper.wrapped).getInputStream(); 548 final ClassReader reader = new ClassReader(bytecode); 549 550 final ClassVisitor writeClass = new WriteClass(); 551 552 // we're doing most of this by hand; we only read the original class to hijack signature, ctor exceptions, 553 // etc.: 554 555 reader.accept(new ClassVisitor(Opcodes.ASM5) { 556 Type supertype; 557 558 @Override 559 public void visit(final int version, final int access, final String name, final String signature, 560 final String superName, final String[] interfaces) { 561 supertype = Type.getObjectType(superName); 562 writeClass.visit(version, Opcodes.ACC_PUBLIC, result, signature, superName, interfaces); 563 564 visitAnnotation(Type.getType(Marker.class).getDescriptor(), false); 565 } 566 567 @Override 568 public MethodVisitor visitMethod(final int access, final String name, final String desc, 569 final String signature, final String[] exceptions) { 570 if (INIT.equals(name)) { 571 572 final Method staticCtor = new Method(INIT, key.getRight()); 573 final Type[] argumentTypes = staticCtor.getArgumentTypes(); 574 final Type[] exceptionTypes = toObjectTypes(exceptions); 575 576 { 577 final GeneratorAdapter mgen = 578 new GeneratorAdapter(Opcodes.ACC_PUBLIC, staticCtor, signature, exceptionTypes, 579 writeClass); 580 mgen.visitCode(); 581 mgen.loadThis(); 582 for (int i = 0; i < argumentTypes.length; i++) { 583 mgen.loadArg(i); 584 } 585 mgen.invokeConstructor(supertype, staticCtor); 586 mgen.returnValue(); 587 mgen.endMethod(); 588 } 589 /* 590 * now declare a dummy constructor that will match, and discard, 591 * any originally inner-class bound constructor i.e. that set up a this$0 field. 592 * By doing this we can avoid playing with the stack that originally 593 * invoked such a constructor and simply rewrite the method 594 */ 595 { 596 final Method instanceCtor = 597 new Method(INIT, Type.VOID_TYPE, ArrayUtils.add(argumentTypes, 0, OBJECT_TYPE)); 598 final GeneratorAdapter mgen = 599 new GeneratorAdapter(Opcodes.ACC_PUBLIC, instanceCtor, signature, exceptionTypes, 600 writeClass); 601 mgen.visitCode(); 602 mgen.loadThis(); 603 for (int i = 0; i < argumentTypes.length; i++) { 604 mgen.loadArg(i + 1); 605 } 606 mgen.invokeConstructor(supertype, staticCtor); 607 mgen.returnValue(); 608 mgen.endMethod(); 609 } 610 return null; 611 } 612 return null; 613 } 614 615 @Override 616 public void visitEnd() { 617 writeClass.visitEnd(); 618 } 619 }, 0); 620 } finally { 621 IOUtils.closeQuietly(bytecode); 622 } 623 return result; 624 } 625 626 /** 627 * Translate internal names to Java type names. 628 * @param types to translate 629 * @return {@link Type}[] 630 * @see Type#getObjectType(String) 631 */ 632 private static Type[] toObjectTypes(final String[] types) { 633 if (types == null) { 634 return null; 635 } 636 final Type[] result = new Type[types.length]; 637 for (int i = 0; i < types.length; i++) { 638 result[i] = Type.getObjectType(types[i]); 639 } 640 return result; 641 } 642}