1 /*
2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
3  *             of Java bytecode.
4  *
5  * Copyright (c) 2002-2014 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  */
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      */
ConfigurationChecker(Configuration configuration)40     public ConfigurationChecker(Configuration configuration)
41     {
42         this.configuration = configuration;
43     }
44 
45 
46     /**
47      * Checks the given configuration for potential problems.
48      */
check()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      */
checkConflicts(ClassPath classPath1, ClassPath classPath2)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