Home | History | Annotate | Download | only in obfuscate
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2009 Eric Lafortune (eric (at) 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  */
     21 package proguard.obfuscate;
     22 
     23 import proguard.*;
     24 import proguard.classfile.*;
     25 import proguard.classfile.attribute.visitor.*;
     26 import proguard.classfile.constant.visitor.AllConstantVisitor;
     27 import proguard.classfile.editor.*;
     28 import proguard.classfile.util.*;
     29 import proguard.classfile.visitor.*;
     30 import proguard.util.*;
     31 
     32 import java.io.*;
     33 import java.util.*;
     34 
     35 /**
     36  * This class can perform obfuscation of class pools according to a given
     37  * specification.
     38  *
     39  * @author Eric Lafortune
     40  */
     41 public class Obfuscator
     42 {
     43     private final Configuration configuration;
     44 
     45 
     46     /**
     47      * Creates a new Obfuscator.
     48      */
     49     public Obfuscator(Configuration configuration)
     50     {
     51         this.configuration = configuration;
     52     }
     53 
     54 
     55     /**
     56      * Performs obfuscation of the given program class pool.
     57      */
     58     public void execute(ClassPool programClassPool,
     59                         ClassPool libraryClassPool) throws IOException
     60     {
     61         // Check if we have at least some keep commands.
     62         if (configuration.keep         == null &&
     63             configuration.applyMapping == null &&
     64             configuration.printMapping == null)
     65         {
     66             throw new IOException("You have to specify '-keep' options for the obfuscation step.");
     67         }
     68 
     69         // Clean up any old visitor info.
     70         programClassPool.classesAccept(new ClassCleaner());
     71         libraryClassPool.classesAccept(new ClassCleaner());
     72 
     73         // If the class member names have to correspond globally,
     74         // link all class members in all classes, otherwise
     75         // link all non-private methods in all class hierarchies.
     76         ClassVisitor memberInfoLinker =
     77             configuration.useUniqueClassMemberNames ?
     78                 (ClassVisitor)new AllMemberVisitor(new MethodLinker()) :
     79                 (ClassVisitor)new BottomClassFilter(new MethodLinker());
     80 
     81         programClassPool.classesAccept(memberInfoLinker);
     82         libraryClassPool.classesAccept(memberInfoLinker);
     83 
     84         // Create a visitor for marking the seeds.
     85         NameMarker nameMarker = new NameMarker();
     86         ClassPoolVisitor classPoolvisitor =
     87             ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
     88                                                                     nameMarker,
     89                                                                     nameMarker,
     90                                                                     false,
     91                                                                     false,
     92                                                                     true);
     93         // Mark the seeds.
     94         programClassPool.accept(classPoolvisitor);
     95         libraryClassPool.accept(classPoolvisitor);
     96 
     97         // All library classes and library class members keep their names.
     98         libraryClassPool.classesAccept(nameMarker);
     99         libraryClassPool.classesAccept(new AllMemberVisitor(nameMarker));
    100 
    101         // Mark attributes that have to be kept.
    102         AttributeUsageMarker requiredAttributeUsageMarker =
    103             new AttributeUsageMarker();
    104 
    105         AttributeVisitor optionalAttributeUsageMarker =
    106             configuration.keepAttributes == null ? null :
    107                 new AttributeNameFilter(new ListParser(new NameParser()).parse(configuration.keepAttributes),
    108                                         requiredAttributeUsageMarker);
    109 
    110         programClassPool.classesAccept(
    111             new AllAttributeVisitor(true,
    112             new RequiredAttributeFilter(requiredAttributeUsageMarker,
    113                                         optionalAttributeUsageMarker)));
    114 
    115         // Remove the attributes that can be discarded. Note that the attributes
    116         // may only be discarded after the seeds have been marked, since the
    117         // configuration may rely on annotations.
    118         programClassPool.classesAccept(new AttributeShrinker());
    119 
    120         // Apply the mapping, if one has been specified. The mapping can
    121         // override the names of library classes and of library class members.
    122         if (configuration.applyMapping != null)
    123         {
    124             WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);
    125 
    126             MappingReader reader = new MappingReader(configuration.applyMapping);
    127 
    128             MappingProcessor keeper =
    129                 new MultiMappingProcessor(new MappingProcessor[]
    130                 {
    131                     new MappingKeeper(programClassPool, warningPrinter),
    132                     new MappingKeeper(libraryClassPool, null),
    133                 });
    134 
    135             reader.pump(keeper);
    136 
    137             // Print out a summary of the warnings if necessary.
    138             int mappingWarningCount = warningPrinter.getWarningCount();
    139             if (mappingWarningCount > 0)
    140             {
    141                 System.err.println("Warning: there were " + mappingWarningCount +
    142                                                             " kept classes and class members that were remapped anyway.");
    143                 System.err.println("         You should adapt your configuration or edit the mapping file.");
    144 
    145                 if (!configuration.ignoreWarnings)
    146                 {
    147                     System.err.println("         If you are sure this remapping won't hurt,");
    148                     System.err.println("         you could try your luck using the '-ignorewarnings' option.");
    149                     throw new IOException("Please correct the above warnings first.");
    150                 }
    151             }
    152         }
    153 
    154         // Come up with new names for all classes.
    155         DictionaryNameFactory classNameFactory = configuration.classObfuscationDictionary != null ?
    156             new DictionaryNameFactory(configuration.classObfuscationDictionary, null) :
    157             null;
    158 
    159         DictionaryNameFactory packageNameFactory = configuration.packageObfuscationDictionary != null ?
    160             new DictionaryNameFactory(configuration.packageObfuscationDictionary, null) :
    161             null;
    162 
    163         programClassPool.classesAccept(
    164             new ClassObfuscator(programClassPool,
    165                                 classNameFactory,
    166                                 packageNameFactory,
    167                                 configuration.useMixedCaseClassNames,
    168                                 configuration.keepPackageNames,
    169                                 configuration.flattenPackageHierarchy,
    170                                 configuration.repackageClasses,
    171                                 configuration.allowAccessModification));
    172 
    173         // Come up with new names for all class members.
    174         NameFactory nameFactory = new SimpleNameFactory();
    175 
    176         if (configuration.obfuscationDictionary != null)
    177         {
    178             nameFactory = new DictionaryNameFactory(configuration.obfuscationDictionary,
    179                                                     nameFactory);
    180         }
    181 
    182         WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);
    183 
    184         // Maintain a map of names to avoid [descriptor - new name - old name].
    185         Map descriptorMap = new HashMap();
    186 
    187         // Do the class member names have to be globally unique?
    188         if (configuration.useUniqueClassMemberNames)
    189         {
    190             // Collect all member names in all classes.
    191             programClassPool.classesAccept(
    192                 new AllMemberVisitor(
    193                 new MemberNameCollector(configuration.overloadAggressively,
    194                                         descriptorMap)));
    195 
    196             // Assign new names to all members in all classes.
    197             programClassPool.classesAccept(
    198                 new AllMemberVisitor(
    199                 new MemberObfuscator(configuration.overloadAggressively,
    200                                      nameFactory,
    201                                      descriptorMap)));
    202         }
    203         else
    204         {
    205             // Come up with new names for all non-private class members.
    206             programClassPool.classesAccept(
    207                 new MultiClassVisitor(new ClassVisitor[]
    208                 {
    209                     // Collect all private member names in this class and down
    210                     // the hierarchy.
    211                     new ClassHierarchyTraveler(true, false, false, true,
    212                     new AllMemberVisitor(
    213                     new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
    214                     new MemberNameCollector(configuration.overloadAggressively,
    215                                             descriptorMap)))),
    216 
    217                     // Collect all non-private member names anywhere in the hierarchy.
    218                     new ClassHierarchyTraveler(true, true, true, true,
    219                     new AllMemberVisitor(
    220                     new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
    221                     new MemberNameCollector(configuration.overloadAggressively,
    222                                             descriptorMap)))),
    223 
    224                     // Assign new names to all non-private members in this class.
    225                     new AllMemberVisitor(
    226                     new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
    227                     new MemberObfuscator(configuration.overloadAggressively,
    228                                          nameFactory,
    229                                          descriptorMap))),
    230 
    231                     // Clear the collected names.
    232                     new MapCleaner(descriptorMap)
    233                 }));
    234 
    235             // Come up with new names for all private class members.
    236             programClassPool.classesAccept(
    237                 new MultiClassVisitor(new ClassVisitor[]
    238                 {
    239                     // Collect all member names in this class.
    240                     new AllMemberVisitor(
    241                     new MemberNameCollector(configuration.overloadAggressively,
    242                                             descriptorMap)),
    243 
    244                     // Collect all non-private member names higher up the hierarchy.
    245                     new ClassHierarchyTraveler(false, true, true, false,
    246                     new AllMemberVisitor(
    247                     new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
    248                     new MemberNameCollector(configuration.overloadAggressively,
    249                                             descriptorMap)))),
    250 
    251                     // Assign new names to all private members in this class.
    252                     new AllMemberVisitor(
    253                     new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
    254                     new MemberObfuscator(configuration.overloadAggressively,
    255                                          nameFactory,
    256                                          descriptorMap))),
    257 
    258                     // Clear the collected names.
    259                     new MapCleaner(descriptorMap)
    260                 }));
    261         }
    262 
    263         // Some class members may have ended up with conflicting names.
    264         // Come up with new, globally unique names for them.
    265         NameFactory specialNameFactory =
    266             new SpecialNameFactory(new SimpleNameFactory());
    267 
    268         // Collect a map of special names to avoid
    269         // [descriptor - new name - old name].
    270         Map specialDescriptorMap = new HashMap();
    271 
    272         programClassPool.classesAccept(
    273             new AllMemberVisitor(
    274             new MemberSpecialNameFilter(
    275             new MemberNameCollector(configuration.overloadAggressively,
    276                                     specialDescriptorMap))));
    277 
    278         libraryClassPool.classesAccept(
    279             new AllMemberVisitor(
    280             new MemberSpecialNameFilter(
    281             new MemberNameCollector(configuration.overloadAggressively,
    282                                     specialDescriptorMap))));
    283 
    284         // Replace conflicting non-private member names with special names.
    285         programClassPool.classesAccept(
    286             new MultiClassVisitor(new ClassVisitor[]
    287             {
    288                 // Collect all private member names in this class and down
    289                 // the hierarchy.
    290                 new ClassHierarchyTraveler(true, false, false, true,
    291                 new AllMemberVisitor(
    292                 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
    293                 new MemberNameCollector(configuration.overloadAggressively,
    294                                         descriptorMap)))),
    295 
    296                 // Collect all non-private member names in this class and
    297                 // higher up the hierarchy.
    298                 new ClassHierarchyTraveler(true, true, true, false,
    299                 new AllMemberVisitor(
    300                 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
    301                 new MemberNameCollector(configuration.overloadAggressively,
    302                                         descriptorMap)))),
    303 
    304                 // Assign new names to all conflicting non-private members
    305                 // in this class and higher up the hierarchy.
    306                 new ClassHierarchyTraveler(true, true, true, false,
    307                 new AllMemberVisitor(
    308                 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
    309                 new MemberNameConflictFixer(configuration.overloadAggressively,
    310                                             descriptorMap,
    311                                             warningPrinter,
    312                 new MemberObfuscator(configuration.overloadAggressively,
    313                                      specialNameFactory,
    314                                      specialDescriptorMap))))),
    315 
    316                 // Clear the collected names.
    317                 new MapCleaner(descriptorMap)
    318             }));
    319 
    320         // Replace conflicting private member names with special names.
    321         // This is only possible if those names were kept or mapped.
    322         programClassPool.classesAccept(
    323             new MultiClassVisitor(new ClassVisitor[]
    324             {
    325                 // Collect all member names in this class.
    326                 new AllMemberVisitor(
    327                 new MemberNameCollector(configuration.overloadAggressively,
    328                                         descriptorMap)),
    329 
    330                 // Collect all non-private member names higher up the hierarchy.
    331                 new ClassHierarchyTraveler(false, true, true, false,
    332                 new AllMemberVisitor(
    333                 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
    334                 new MemberNameCollector(configuration.overloadAggressively,
    335                                         descriptorMap)))),
    336 
    337                 // Assign new names to all conflicting private members in this
    338                 // class.
    339                 new AllMemberVisitor(
    340                 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
    341                 new MemberNameConflictFixer(configuration.overloadAggressively,
    342                                             descriptorMap,
    343                                             warningPrinter,
    344                 new MemberObfuscator(configuration.overloadAggressively,
    345                                      specialNameFactory,
    346                                      specialDescriptorMap)))),
    347 
    348                 // Clear the collected names.
    349                 new MapCleaner(descriptorMap)
    350             }));
    351 
    352         // Print out any warnings about member name conflicts.
    353         int warningCount = warningPrinter.getWarningCount();
    354         if (warningCount > 0)
    355         {
    356             System.err.println("Warning: there were " + warningCount +
    357                                " conflicting class member name mappings.");
    358             System.err.println("         Your configuration may be inconsistent.");
    359 
    360             if (!configuration.ignoreWarnings)
    361             {
    362                 System.err.println("         If you are sure the conflicts are harmless,");
    363                 System.err.println("         you could try your luck using the '-ignorewarnings' option.");
    364                 throw new IOException("Please correct the above warnings first.");
    365             }
    366         }
    367 
    368         // Print out the mapping, if requested.
    369         if (configuration.printMapping != null)
    370         {
    371             PrintStream ps = isFile(configuration.printMapping) ?
    372                 new PrintStream(new BufferedOutputStream(new FileOutputStream(configuration.printMapping))) :
    373                 System.out;
    374 
    375             // Print out items that will be removed.
    376             programClassPool.classesAcceptAlphabetically(new MappingPrinter(ps));
    377 
    378             if (ps != System.out)
    379             {
    380                 ps.close();
    381             }
    382         }
    383 
    384         // Actually apply the new names.
    385         programClassPool.classesAccept(new ClassRenamer());
    386         libraryClassPool.classesAccept(new ClassRenamer());
    387 
    388         // Update all references to these new names.
    389         programClassPool.classesAccept(new ClassReferenceFixer(false));
    390         libraryClassPool.classesAccept(new ClassReferenceFixer(false));
    391         programClassPool.classesAccept(new MemberReferenceFixer());
    392 
    393         // Make package visible elements public or protected, if obfuscated
    394         // classes are being repackaged aggressively.
    395         if (configuration.repackageClasses != null &&
    396             configuration.allowAccessModification)
    397         {
    398             programClassPool.classesAccept(
    399                 new AllConstantVisitor(
    400                 new AccessFixer()));
    401         }
    402 
    403         // Rename the source file attributes, if requested.
    404         if (configuration.newSourceFileAttribute != null)
    405         {
    406             programClassPool.classesAccept(new SourceFileRenamer(configuration.newSourceFileAttribute));
    407         }
    408 
    409         // Mark NameAndType constant pool entries that have to be kept
    410         // and remove the other ones.
    411         programClassPool.classesAccept(new NameAndTypeUsageMarker());
    412         programClassPool.classesAccept(new NameAndTypeShrinker());
    413 
    414         // Mark Utf8 constant pool entries that have to be kept
    415         // and remove the other ones.
    416         programClassPool.classesAccept(new Utf8UsageMarker());
    417         programClassPool.classesAccept(new Utf8Shrinker());
    418     }
    419 
    420 
    421     /**
    422      * Returns whether the given file is actually a file, or just a placeholder
    423      * for the standard output.
    424      */
    425     private boolean isFile(File file)
    426     {
    427         return file.getPath().length() > 0;
    428     }
    429 }
    430