1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard.optimize.peephole;
22
23import proguard.classfile.*;
24import proguard.classfile.attribute.visitor.AttributeNameFilter;
25import proguard.classfile.constant.visitor.*;
26import proguard.classfile.editor.*;
27import proguard.classfile.util.*;
28import proguard.classfile.visitor.*;
29import proguard.optimize.KeepMarker;
30import proguard.optimize.info.*;
31import proguard.util.*;
32
33import java.util.*;
34
35/**
36 * This ClassVisitor inlines the classes that it visits in a given target class,
37 * whenever possible.
38 *
39 * @see RetargetedInnerClassAttributeRemover
40 * @see TargetClassChanger
41 * @see ClassReferenceFixer
42 * @see MemberReferenceFixer
43 * @see AccessFixer
44 * @author Eric Lafortune
45 */
46public class ClassMerger
47extends      SimplifiedVisitor
48implements   ClassVisitor,
49             ConstantVisitor
50{
51    //*
52    private static final boolean DEBUG = false;
53    /*/
54    private static       boolean DEBUG = System.getProperty("cm") != null;
55    //*/
56
57
58    private final ProgramClass targetClass;
59    private final boolean      allowAccessModification;
60    private final boolean      mergeInterfacesAggressively;
61    private final ClassVisitor extraClassVisitor;
62
63    private final MemberVisitor fieldOptimizationInfoCopier = new FieldOptimizationInfoCopier();
64
65
66    /**
67     * Creates a new ClassMerger that will merge classes into the given target
68     * class.
69     * @param targetClass                 the class into which all visited
70     *                                    classes will be merged.
71     * @param allowAccessModification     specifies whether the access modifiers
72     *                                    of classes can be changed in order to
73     *                                    merge them.
74     * @param mergeInterfacesAggressively specifies whether interfaces may
75     *                                    be merged aggressively.
76     */
77    public ClassMerger(ProgramClass targetClass,
78                       boolean      allowAccessModification,
79                       boolean      mergeInterfacesAggressively)
80    {
81        this(targetClass, allowAccessModification, mergeInterfacesAggressively, null);
82    }
83
84
85    /**
86     * Creates a new ClassMerger that will merge classes into the given target
87     * class.
88     * @param targetClass                 the class into which all visited
89     *                                    classes will be merged.
90     * @param allowAccessModification     specifies whether the access modifiers
91     *                                    of classes can be changed in order to
92     *                                    merge them.
93     * @param mergeInterfacesAggressively specifies whether interfaces may
94     *                                    be merged aggressively.
95     * @param extraClassVisitor           an optional extra visitor for all
96     *                                    merged classes.
97     */
98    public ClassMerger(ProgramClass targetClass,
99                       boolean      allowAccessModification,
100                       boolean      mergeInterfacesAggressively,
101                       ClassVisitor extraClassVisitor)
102    {
103        this.targetClass                 = targetClass;
104        this.allowAccessModification     = allowAccessModification;
105        this.mergeInterfacesAggressively = mergeInterfacesAggressively;
106        this.extraClassVisitor           = extraClassVisitor;
107    }
108
109
110    // Implementations for ClassVisitor.
111
112    public void visitProgramClass(ProgramClass programClass)
113    {
114        //final String CLASS_NAME = "abc/Def";
115        //DEBUG = programClass.getName().equals(CLASS_NAME) ||
116        //        targetClass.getName().equals(CLASS_NAME);
117
118        // TODO: Remove this when the class merger has stabilized.
119        // Catch any unexpected exceptions from the actual visiting method.
120        try
121        {
122            visitProgramClass0(programClass);
123        }
124        catch (RuntimeException ex)
125        {
126            System.err.println("Unexpected error while merging classes:");
127            System.err.println("  Class        = ["+programClass.getName()+"]");
128            System.err.println("  Target class = ["+targetClass.getName()+"]");
129            System.err.println("  Exception    = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
130
131            if (DEBUG)
132            {
133                programClass.accept(new ClassPrinter());
134                targetClass.accept(new ClassPrinter());
135            }
136
137            throw ex;
138        }
139    }
140
141    public void visitProgramClass0(ProgramClass programClass)
142    {
143        if (!programClass.equals(targetClass) &&
144
145            // Don't merge classes that must be preserved.
146            !KeepMarker.isKept(programClass) &&
147            !KeepMarker.isKept(targetClass)  &&
148
149            // Only merge classes that haven't been retargeted yet.
150            getTargetClass(programClass) == null &&
151            getTargetClass(targetClass)  == null &&
152
153            // Don't merge annotation classes, with all their introspection and
154            // infinite recursion.
155            (programClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_ANNOTATTION) == 0 &&
156
157            // Only merge classes if we can change the access permissions, or
158            // if they are in the same package, or
159            // if they are public and don't contain or invoke package visible
160            // class members.
161            (allowAccessModification                                                        ||
162             ((programClass.getAccessFlags() &
163               targetClass.getAccessFlags()  &
164               ClassConstants.INTERNAL_ACC_PUBLIC) != 0 &&
165              !PackageVisibleMemberContainingClassMarker.containsPackageVisibleMembers(programClass) &&
166              !PackageVisibleMemberInvokingClassMarker.invokesPackageVisibleMembers(programClass)) ||
167             ClassUtil.internalPackageName(programClass.getName()).equals(
168             ClassUtil.internalPackageName(targetClass.getName()))) &&
169
170            // Only merge two classes or two interfaces or two abstract classes,
171            // or a class into an interface with a single implementation.
172            ((programClass.getAccessFlags() &
173              (ClassConstants.INTERNAL_ACC_INTERFACE |
174               ClassConstants.INTERNAL_ACC_ABSTRACT)) ==
175             (targetClass.getAccessFlags()  &
176              (ClassConstants.INTERNAL_ACC_INTERFACE |
177               ClassConstants.INTERNAL_ACC_ABSTRACT)) ||
178             (isOnlySubClass(programClass, targetClass) &&
179              (programClass.getSuperClass().equals(targetClass) ||
180               programClass.getSuperClass().equals(targetClass.getSuperClass())))) &&
181
182            // One class must not implement the other class indirectly.
183            !indirectlyImplementedInterfaces(programClass).contains(targetClass) &&
184            !targetClass.extendsOrImplements(programClass) &&
185
186            // The two classes must have the same superclasses and interfaces
187            // with static initializers.
188            initializedSuperClasses(programClass).equals(initializedSuperClasses(targetClass))   &&
189
190            // The two classes must have the same superclasses and interfaces
191            // that are tested with 'instanceof'.
192            instanceofedSuperClasses(programClass).equals(instanceofedSuperClasses(targetClass)) &&
193
194            // The two classes must have the same superclasses that are caught
195            // as exceptions.
196            caughtSuperClasses(programClass).equals(caughtSuperClasses(targetClass)) &&
197
198            // The two classes must not both be part of a .class construct.
199            !(DotClassMarker.isDotClassed(programClass) &&
200              DotClassMarker.isDotClassed(targetClass)) &&
201
202            // The classes must not have clashing fields.
203            !haveAnyIdenticalFields(programClass, targetClass) &&
204
205            // The two classes must not introduce any unwanted fields.
206            !introducesUnwantedFields(programClass, targetClass) &&
207            !introducesUnwantedFields(targetClass, programClass) &&
208
209            // The two classes must not shadow each others fields.
210            !shadowsAnyFields(programClass, targetClass) &&
211            !shadowsAnyFields(targetClass, programClass) &&
212
213            // The classes must not have clashing methods.
214            !haveAnyIdenticalMethods(programClass, targetClass) &&
215
216            // The classes must not introduce abstract methods, unless
217            // explicitly allowed.
218            (mergeInterfacesAggressively ||
219             (!introducesUnwantedAbstractMethods(programClass, targetClass) &&
220              !introducesUnwantedAbstractMethods(targetClass, programClass))) &&
221
222            // The classes must not override each others concrete methods.
223            !overridesAnyMethods(programClass, targetClass) &&
224            !overridesAnyMethods(targetClass, programClass) &&
225
226            // The classes must not shadow each others non-private methods.
227            !shadowsAnyMethods(programClass, targetClass) &&
228            !shadowsAnyMethods(targetClass, programClass))
229        {
230            if (DEBUG)
231            {
232                System.out.println("ClassMerger ["+programClass.getName()+"] -> ["+targetClass.getName()+"]");
233                System.out.println("  Source interface? ["+((programClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE)!=0)+"]");
234                System.out.println("  Target interface? ["+((targetClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE)!=0)+"]");
235                System.out.println("  Source subclasses ["+programClass.subClasses+"]");
236                System.out.println("  Target subclasses ["+targetClass.subClasses+"]");
237                System.out.println("  Source superclass ["+programClass.getSuperClass().getName()+"]");
238                System.out.println("  Target superclass ["+targetClass.getSuperClass().getName()+"]");
239
240                //System.out.println("=== Before ===");
241                //programClass.accept(new ClassPrinter());
242                //targetClass.accept(new ClassPrinter());
243            }
244
245            // Combine the access flags.
246            int targetAccessFlags = targetClass.getAccessFlags();
247            int sourceAccessFlags = programClass.getAccessFlags();
248
249            targetClass.u2accessFlags =
250                ((targetAccessFlags &
251                  sourceAccessFlags) &
252                 (ClassConstants.INTERNAL_ACC_INTERFACE |
253                  ClassConstants.INTERNAL_ACC_ABSTRACT)) |
254                ((targetAccessFlags |
255                  sourceAccessFlags) &
256                 (ClassConstants.INTERNAL_ACC_PUBLIC      |
257                  ClassConstants.INTERNAL_ACC_SUPER       |
258                  ClassConstants.INTERNAL_ACC_ANNOTATTION |
259                  ClassConstants.INTERNAL_ACC_ENUM));
260
261            // Copy over the superclass, unless it's the target class itself.
262            //if (!targetClass.getName().equals(programClass.getSuperName()))
263            //{
264            //    targetClass.u2superClass =
265            //        new ConstantAdder(targetClass).addConstant(programClass, programClass.u2superClass);
266            //}
267
268            // Copy over the interfaces that aren't present yet and that
269            // wouldn't cause loops in the class hierarchy.
270            programClass.interfaceConstantsAccept(
271                new ExceptClassConstantFilter(targetClass.getName(),
272                new ImplementedClassConstantFilter(targetClass,
273                new ImplementingClassConstantFilter(targetClass,
274                new InterfaceAdder(targetClass)))));
275
276            // Copy over the class members.
277            MemberAdder memberAdder =
278                new MemberAdder(targetClass, fieldOptimizationInfoCopier);
279
280            programClass.fieldsAccept(memberAdder);
281            programClass.methodsAccept(memberAdder);
282
283            // Copy over the other attributes.
284            programClass.attributesAccept(
285                new AttributeNameFilter(new NotMatcher(new OrMatcher(new OrMatcher(
286                    new FixedStringMatcher(ClassConstants.ATTR_SourceFile),
287                    new FixedStringMatcher(ClassConstants.ATTR_InnerClasses)),
288                    new FixedStringMatcher(ClassConstants.ATTR_EnclosingMethod))),
289                new AttributeAdder(targetClass, true)));
290
291            // Update the optimization information of the target class.
292            ClassOptimizationInfo info =
293                ClassOptimizationInfo.getClassOptimizationInfo(targetClass);
294            if (info != null)
295            {
296                info.merge(ClassOptimizationInfo.getClassOptimizationInfo(programClass));
297            }
298
299            // Remember to replace the inlined class by the target class.
300            setTargetClass(programClass, targetClass);
301
302            //if (DEBUG)
303            //{
304            //    System.out.println("=== After ====");
305            //    targetClass.accept(new ClassPrinter());
306            //}
307
308            // Visit the merged class, if required.
309            if (extraClassVisitor != null)
310            {
311                extraClassVisitor.visitProgramClass(programClass);
312            }
313        }
314    }
315
316
317    // Small utility methods.
318
319    /**
320     * Returns whether a given class is the only subclass of another given class.
321     */
322    private boolean isOnlySubClass(Clazz        subClass,
323                                   ProgramClass clazz)
324    {
325        // TODO: The list of subclasses is not up to date.
326        return clazz.subClasses != null     &&
327               clazz.subClasses.length == 1 &&
328               clazz.subClasses[0].equals(subClass);
329    }
330
331
332    /**
333     * Returns the set of indirectly implemented interfaces.
334     */
335    private Set indirectlyImplementedInterfaces(Clazz clazz)
336    {
337        Set set = new HashSet();
338
339        ReferencedClassVisitor referencedInterfaceCollector =
340            new ReferencedClassVisitor(
341            new ClassHierarchyTraveler(false, false, true, false,
342            new ClassCollector(set)));
343
344        // Visit all superclasses and  collect their interfaces.
345        clazz.superClassConstantAccept(referencedInterfaceCollector);
346
347        // Visit all interfaces and collect their interfaces.
348        clazz.interfaceConstantsAccept(referencedInterfaceCollector);
349
350        return set;
351    }
352
353
354    /**
355     * Returns the set of superclasses and interfaces that are initialized.
356     */
357    private Set initializedSuperClasses(Clazz clazz)
358    {
359        Set set = new HashSet();
360
361        // Visit all superclasses and interfaces, collecting the ones that have
362        // static initializers.
363        clazz.hierarchyAccept(true, true, true, false,
364                              new StaticInitializerContainingClassFilter(
365                              new ClassCollector(set)));
366
367        return set;
368    }
369
370
371    /**
372     * Returns the set of superclasses and interfaces that are used in
373     * 'instanceof' tests.
374     */
375    private Set instanceofedSuperClasses(Clazz clazz)
376    {
377        Set set = new HashSet();
378
379        // Visit all superclasses and interfaces, collecting the ones that are
380        // used in an 'instanceof' test.
381        clazz.hierarchyAccept(true, true, true, false,
382                              new InstanceofClassFilter(
383                              new ClassCollector(set)));
384
385        return set;
386    }
387
388
389    /**
390     * Returns the set of superclasses that are caught as exceptions.
391     */
392    private Set caughtSuperClasses(Clazz clazz)
393    {
394        // Don't bother if this isn't an exception at all.
395        if (!clazz.extends_(ClassConstants.INTERNAL_NAME_JAVA_LANG_THROWABLE))
396        {
397            return Collections.EMPTY_SET;
398        }
399
400        // Visit all superclasses, collecting the ones that are caught
401        // (plus java.lang.Object, in the current implementation).
402        Set set = new HashSet();
403
404        clazz.hierarchyAccept(true, true, false, false,
405                              new CaughtClassFilter(
406                              new ClassCollector(set)));
407
408        return set;
409    }
410
411
412    /**
413     * Returns whether the two given classes have class members with the same
414     * name and descriptor.
415     */
416    private boolean haveAnyIdenticalFields(Clazz clazz, Clazz targetClass)
417    {
418        MemberCounter counter = new MemberCounter();
419
420        // Visit all fields, counting the with the same name and descriptor in
421        // the target class.
422        clazz.fieldsAccept(new SimilarMemberVisitor(targetClass, true, false, false, false,
423                           counter));
424
425        return counter.getCount() > 0;
426    }
427
428
429    /**
430     * Returns whether the given class would introduce any unwanted fields
431     * in the target class.
432     */
433    private boolean introducesUnwantedFields(ProgramClass programClass,
434                                             ProgramClass targetClass)
435    {
436        // It's ok if the target class is never instantiated, without any other
437        // subclasses except for maybe the source class.
438        if (!InstantiationClassMarker.isInstantiated(targetClass) &&
439            (targetClass.subClasses == null ||
440             isOnlySubClass(programClass, targetClass)))
441        {
442            return false;
443        }
444
445        MemberCounter counter = new MemberCounter();
446
447        // Count all non-static fields in the the source class.
448        programClass.fieldsAccept(new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_STATIC,
449                                  counter));
450
451        return counter.getCount() > 0;
452    }
453
454
455    /**
456     * Returns whether the given class or its subclasses shadow any fields in
457     * the given target class.
458     */
459    private boolean shadowsAnyFields(Clazz clazz, Clazz targetClass)
460    {
461        MemberCounter counter = new MemberCounter();
462
463        // Visit all fields, counting the ones that are shadowing non-private
464        // fields in the class hierarchy of the target class.
465        clazz.hierarchyAccept(true, false, false, true,
466                              new AllFieldVisitor(
467                              new SimilarMemberVisitor(targetClass, true, true, true, false,
468                              new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
469                              counter))));
470
471        return counter.getCount() > 0;
472    }
473
474
475    /**
476     * Returns whether the two given classes have class members with the same
477     * name and descriptor.
478     */
479    private boolean haveAnyIdenticalMethods(Clazz clazz, Clazz targetClass)
480    {
481        MemberCounter counter = new MemberCounter();
482
483        // Visit all non-abstract methods, counting the ones that are also
484        // present in the target class.
485        clazz.methodsAccept(new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_ABSTRACT,
486                            new SimilarMemberVisitor(targetClass, true, false, false, false,
487                            new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_ABSTRACT,
488                            counter))));
489
490        return counter.getCount() > 0;
491    }
492
493
494    /**
495     * Returns whether the given class would introduce any abstract methods
496     * in the target class.
497     */
498    private boolean introducesUnwantedAbstractMethods(Clazz        clazz,
499                                                      ProgramClass targetClass)
500    {
501        // It's ok if the target class is already abstract and it has at most
502        // the class as a subclass.
503        if ((targetClass.getAccessFlags() &
504             (ClassConstants.INTERNAL_ACC_ABSTRACT |
505              ClassConstants.INTERNAL_ACC_INTERFACE)) != 0 &&
506            (targetClass.subClasses == null ||
507             isOnlySubClass(clazz, targetClass)))
508        {
509            return false;
510        }
511
512        MemberCounter counter   = new MemberCounter();
513        Set           targetSet = new HashSet();
514
515        // Collect all abstract methods, and similar abstract methods in the
516        // class hierarchy of the target class.
517        clazz.methodsAccept(new MemberAccessFilter(ClassConstants.INTERNAL_ACC_ABSTRACT, 0,
518                            new MultiMemberVisitor(new MemberVisitor[]
519                            {
520                                counter,
521                                new SimilarMemberVisitor(targetClass, true, true, true, false,
522                                                         new MemberAccessFilter(ClassConstants.INTERNAL_ACC_ABSTRACT, 0,
523                                                         new MemberCollector(targetSet)))
524                            })));
525
526        return targetSet.size() < counter.getCount();
527    }
528
529
530    /**
531     * Returns whether the given class overrides any methods in the given
532     * target class.
533     */
534    private boolean overridesAnyMethods(Clazz clazz, Clazz targetClass)
535    {
536        MemberCounter counter = new MemberCounter();
537
538        // Visit all non-private non-static methods, counting the ones that are
539        // being overridden in the class hierarchy of the target class.
540        clazz.methodsAccept(new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE | ClassConstants.INTERNAL_ACC_STATIC | ClassConstants.INTERNAL_ACC_ABSTRACT,
541                            new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_CLINIT)),
542                            new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_INIT)),
543                            new SimilarMemberVisitor(targetClass, true, true, false, false,
544                            new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE | ClassConstants.INTERNAL_ACC_STATIC | ClassConstants.INTERNAL_ACC_ABSTRACT,
545                            counter))))));
546
547        return counter.getCount() > 0;
548    }
549
550
551    /**
552     * Returns whether the given class or its subclasses shadow any methods in
553     * the given target class.
554     */
555    private boolean shadowsAnyMethods(Clazz clazz, Clazz targetClass)
556    {
557        MemberCounter counter = new MemberCounter();
558
559        // Visit all private methods, counting the ones that are shadowing
560        // non-private methods in the class hierarchy of the target class.
561        clazz.hierarchyAccept(true, false, false, true,
562                              new AllMethodVisitor(
563                              new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
564                              new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_INIT)),
565                              new SimilarMemberVisitor(targetClass, true, true, true, false,
566                              new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
567                              counter))))));
568
569        // Visit all static methods, counting the ones that are shadowing
570        // non-private methods in the class hierarchy of the target class.
571        clazz.hierarchyAccept(true, false, false, true,
572                              new AllMethodVisitor(
573                              new MemberAccessFilter(ClassConstants.INTERNAL_ACC_STATIC, 0,
574                              new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_CLINIT)),
575                              new SimilarMemberVisitor(targetClass, true, true, true, false,
576                              new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
577                              counter))))));
578
579        return counter.getCount() > 0;
580    }
581
582
583    public static void setTargetClass(Clazz clazz, Clazz targetClass)
584    {
585        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
586        if (info != null)
587        {
588            info.setTargetClass(targetClass);
589        }
590    }
591
592
593    public static Clazz getTargetClass(Clazz clazz)
594    {
595        Clazz targetClass = null;
596
597        // Return the last target class, if any.
598        while (true)
599        {
600            ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
601            if (info == null)
602            {
603                return targetClass;
604            }
605
606            clazz = info.getTargetClass();
607            if (clazz == null)
608            {
609                return targetClass;
610            }
611
612            targetClass = clazz;
613        }
614    }
615
616
617    /**
618     * This MemberVisitor copies field optimization info from copied fields.
619     */
620    private static class FieldOptimizationInfoCopier
621    extends              SimplifiedVisitor
622    implements           MemberVisitor
623    {
624        public void visitProgramField(ProgramClass programClass, ProgramField programField)
625        {
626            // Copy the optimization info from the field that was just copied.
627            ProgramField copiedField = (ProgramField)programField.getVisitorInfo();
628            Object       info        = copiedField.getVisitorInfo();
629
630            programField.setVisitorInfo(info instanceof FieldOptimizationInfo ?
631                new FieldOptimizationInfo((FieldOptimizationInfo)info) :
632                info);
633        }
634
635
636        public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
637        {
638            // Linked methods share their optimization info.
639        }
640    }
641}