Home | History | Annotate | Download | only in proguard
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2014 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;
     22 
     23 import proguard.classfile.util.WarningPrinter;
     24 
     25 import java.io.IOException;
     26 
     27 /**
     28  * This class performs sanity checks on a given configurations.
     29  *
     30  * @author Eric Lafortune
     31  */
     32 public class ConfigurationChecker
     33 {
     34     private final Configuration configuration;
     35 
     36 
     37     /**
     38      * Creates a new ConfigurationChecker with the given configuration.
     39      */
     40     public ConfigurationChecker(Configuration configuration)
     41     {
     42         this.configuration = configuration;
     43     }
     44 
     45 
     46     /**
     47      * Checks the given configuration for potential problems.
     48      */
     49     public void check() throws IOException
     50     {
     51         ClassPath programJars = configuration.programJars;
     52         ClassPath libraryJars = configuration.libraryJars;
     53 
     54         // Check that the input isn't empty.
     55         if (programJars == null)
     56         {
     57             throw new IOException("The input is empty. You have to specify one or more '-injars' options.");
     58         }
     59 
     60         // Check that the first jar is an input jar.
     61         ClassPathEntry firstEntry = programJars.get(0);
     62         if (firstEntry.isOutput())
     63         {
     64             throw new IOException("The output jar [" + firstEntry.getName() +
     65                                   "] must be specified after an input jar, or it will be empty.");
     66         }
     67 
     68         // Check that the first of two subsequent the output jars has a filter.
     69         for (int index = 0; index < programJars.size() - 1; index++)
     70         {
     71             ClassPathEntry entry = programJars.get(index);
     72             if (entry.isOutput()    &&
     73                 !entry.isFiltered() &&
     74                 programJars.get(index + 1).isOutput())
     75             {
     76                 throw new IOException("The output jar [" + entry.getName() +
     77                                       "] must have a filter, or all subsequent output jars will be empty.");
     78             }
     79         }
     80 
     81         // Check for conflicts between input/output entries of the class paths.
     82         checkConflicts(programJars, programJars);
     83         checkConflicts(programJars, libraryJars);
     84         checkConflicts(libraryJars, libraryJars);
     85 
     86         // Print out some general notes if necessary.
     87         if ((configuration.note == null ||
     88              !configuration.note.isEmpty()))
     89         {
     90             // Check for potential problems with mixed-case class names on
     91             // case-insensitive file systems.
     92             if (configuration.obfuscate &&
     93                 configuration.useMixedCaseClassNames &&
     94                 configuration.classObfuscationDictionary == null)
     95             {
     96                 String os = System.getProperty("os.name").toLowerCase();
     97                 if (os.startsWith("windows") ||
     98                     os.startsWith("mac os"))
     99                 {
    100                     // Go over all program class path entries.
    101                     for (int index = 0; index < programJars.size(); index++)
    102                     {
    103                         // Is it an output directory?
    104                         ClassPathEntry entry = programJars.get(index);
    105                         if (entry.isOutput() &&
    106                             !entry.isApk() &&
    107                             !entry.isJar() &&
    108                             !entry.isAar() &&
    109                             !entry.isWar() &&
    110                             !entry.isEar() &&
    111                             !entry.isZip())
    112                         {
    113                             System.out.println("Note: you're writing the processed class files to a directory [" + entry.getName() + "].");
    114                             System.out.println("      This will likely cause problems with obfuscated mixed-case class names.");
    115                             System.out.println("      You should consider writing the output to a jar file, or otherwise");
    116                             System.out.println("      specify '-dontusemixedcaseclassnames'.");
    117 
    118                             break;
    119                         }
    120                     }
    121                 }
    122             }
    123 
    124             // Check if -adaptresourcefilecontents has a proper filter.
    125             if (configuration.adaptResourceFileContents != null &&
    126                 (configuration.adaptResourceFileContents.isEmpty() ||
    127                  configuration.adaptResourceFileContents.get(0).equals(ConfigurationConstants.ANY_FILE_KEYWORD)))
    128             {
    129                 System.out.println("Note: you're specifying '-adaptresourcefilecontents' for all resource files.");
    130                 System.out.println("      This will most likely cause problems with binary files.");
    131             }
    132 
    133             // Check if all -keepclassmembers options indeed have class members.
    134             WarningPrinter keepClassMemberNotePrinter = new WarningPrinter(System.out, configuration.note);
    135 
    136             new KeepClassMemberChecker(keepClassMemberNotePrinter).checkClassSpecifications(configuration.keep);
    137 
    138             // Check if -assumenosideffects options don't specify all methods.
    139             WarningPrinter assumeNoSideEffectsNotePrinter = new WarningPrinter(System.out, configuration.note);
    140 
    141             new AssumeNoSideEffectsChecker(assumeNoSideEffectsNotePrinter).checkClassSpecifications(configuration.assumeNoSideEffects);
    142 
    143             // Print out a summary of the notes, if necessary.
    144             int keepClassMemberNoteCount = keepClassMemberNotePrinter.getWarningCount();
    145             if (keepClassMemberNoteCount > 0)
    146             {
    147                 System.out.println("Note: there were " + keepClassMemberNoteCount +
    148                                    " '-keepclassmembers' options that didn't specify class");
    149                 System.out.println("      members. You should specify at least some class members or consider");
    150                 System.out.println("      if you just need '-keep'.");
    151                 System.out.println("      (http://proguard.sourceforge.net/manual/troubleshooting.html#classmembers)");
    152             }
    153 
    154             int assumeNoSideEffectsNoteCount = assumeNoSideEffectsNotePrinter.getWarningCount();
    155             if (assumeNoSideEffectsNoteCount > 0)
    156             {
    157                 System.out.println("Note: there were " + assumeNoSideEffectsNoteCount +
    158                                    " '-assumenosideeffects' options that try to match all");
    159                 System.out.println("      methods with wildcards. This will likely cause problems with methods like");
    160                 System.out.println("      'wait()' and 'notify()'. You should specify the methods more precisely.");
    161                 System.out.println("      (http://proguard.sourceforge.net/manual/troubleshooting.html#nosideeffects)");
    162             }
    163         }
    164     }
    165 
    166 
    167     /**
    168      * Performs some sanity checks on the class paths.
    169      */
    170     private void checkConflicts(ClassPath classPath1,
    171                                 ClassPath classPath2)
    172     throws IOException
    173     {
    174         if (classPath1 == null ||
    175             classPath2 == null)
    176         {
    177             return;
    178         }
    179 
    180         for (int index1 = 0; index1 < classPath1.size(); index1++)
    181         {
    182             ClassPathEntry entry1 = classPath1.get(index1);
    183 
    184             for (int index2 = 0; index2 < classPath2.size(); index2++)
    185             {
    186                 if (classPath1 != classPath2 || index1 != index2)
    187                 {
    188                     ClassPathEntry entry2 = classPath2.get(index2);
    189 
    190                     if (entry2.getName().equals(entry1.getName()))
    191                     {
    192                         if (entry1.isOutput())
    193                         {
    194                             if (entry2.isOutput())
    195                             {
    196                                 // Output / output.
    197                                 throw new IOException("The same output jar ["+entry1.getName()+"] is specified twice.");
    198                             }
    199                             else
    200                             {
    201                                 // Output / input.
    202                                 throw new IOException("Input jars and output jars must be different ["+entry1.getName()+"].");
    203                             }
    204                         }
    205                         else
    206                         {
    207                             if (entry2.isOutput())
    208                             {
    209                                 // Input / output.
    210                                 throw new IOException("Input jars and output jars must be different ["+entry1.getName()+"].");
    211                             }
    212                             else if (!entry1.isFiltered() ||
    213                                      !entry2.isFiltered())
    214                             {
    215                                 // Input / input.
    216                                 throw new IOException("The same input jar ["+entry1.getName()+"] is specified twice.");
    217                             }
    218                         }
    219                     }
    220                 }
    221             }
    222         }
    223     }
    224 }
    225