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