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}