1/* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2009 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; 22 23import proguard.classfile.ClassPool; 24import proguard.classfile.editor.ClassElementSorter; 25import proguard.classfile.visitor.*; 26import proguard.obfuscate.Obfuscator; 27import proguard.optimize.Optimizer; 28import proguard.preverify.*; 29import proguard.shrink.Shrinker; 30 31import java.io.*; 32 33/** 34 * Tool for shrinking, optimizing, obfuscating, and preverifying Java classes. 35 * 36 * @author Eric Lafortune 37 */ 38public class ProGuard 39{ 40 public static final String VERSION = "ProGuard, version 4.4"; 41 42 private final Configuration configuration; 43 private ClassPool programClassPool = new ClassPool(); 44 private final ClassPool libraryClassPool = new ClassPool(); 45 46 47 /** 48 * Creates a new ProGuard object to process jars as specified by the given 49 * configuration. 50 */ 51 public ProGuard(Configuration configuration) 52 { 53 this.configuration = configuration; 54 } 55 56 57 /** 58 * Performs all subsequent ProGuard operations. 59 */ 60 public void execute() throws IOException 61 { 62 System.out.println(VERSION); 63 64 GPL.check(); 65 66 if (configuration.printConfiguration != null) 67 { 68 printConfiguration(); 69 } 70 71 if (configuration.programJars != null && 72 configuration.programJars.hasOutput() && 73 new UpToDateChecker(configuration).check()) 74 { 75 return; 76 } 77 78 readInput(); 79 80 if (configuration.shrink || 81 configuration.optimize || 82 configuration.obfuscate || 83 configuration.preverify) 84 { 85 initialize(); 86 } 87 88 if (configuration.targetClassVersion != 0) 89 { 90 target(); 91 } 92 93 if (configuration.printSeeds != null) 94 { 95 printSeeds(); 96 } 97 98 if (configuration.shrink) 99 { 100 shrink(); 101 } 102 103 if (configuration.preverify) 104 { 105 inlineSubroutines(); 106 } 107 108 if (configuration.optimize) 109 { 110 for (int optimizationPass = 0; 111 optimizationPass < configuration.optimizationPasses; 112 optimizationPass++) 113 { 114 if (!optimize()) 115 { 116 // Stop optimizing if the code doesn't improve any further. 117 break; 118 } 119 120 // Shrink again, if we may. 121 if (configuration.shrink) 122 { 123 // Don't print any usage this time around. 124 configuration.printUsage = null; 125 configuration.whyAreYouKeeping = null; 126 127 shrink(); 128 } 129 } 130 } 131 132 if (configuration.obfuscate) 133 { 134 obfuscate(); 135 } 136 137 if (configuration.preverify) 138 { 139 preverify(); 140 } 141 142 if (configuration.shrink || 143 configuration.optimize || 144 configuration.obfuscate || 145 configuration.preverify) 146 { 147 sortClassElements(); 148 } 149 150 if (configuration.programJars.hasOutput()) 151 { 152 writeOutput(); 153 } 154 155 if (configuration.dump != null) 156 { 157 dump(); 158 } 159 } 160 161 162 /** 163 * Prints out the configuration that ProGuard is using. 164 */ 165 private void printConfiguration() throws IOException 166 { 167 if (configuration.verbose) 168 { 169 System.out.println("Printing configuration to [" + fileName(configuration.printConfiguration) + "]..."); 170 } 171 172 PrintStream ps = createPrintStream(configuration.printConfiguration); 173 try 174 { 175 new ConfigurationWriter(ps).write(configuration); 176 } 177 finally 178 { 179 closePrintStream(ps); 180 } 181 } 182 183 184 /** 185 * Reads the input class files. 186 */ 187 private void readInput() throws IOException 188 { 189 if (configuration.verbose) 190 { 191 System.out.println("Reading input..."); 192 } 193 194 // Fill the program class pool and the library class pool. 195 new InputReader(configuration).execute(programClassPool, libraryClassPool); 196 } 197 198 199 /** 200 * Initializes the cross-references between all classes, performs some 201 * basic checks, and shrinks the library class pool. 202 */ 203 private void initialize() throws IOException 204 { 205 if (configuration.verbose) 206 { 207 System.out.println("Initializing..."); 208 } 209 210 new Initializer(configuration).execute(programClassPool, libraryClassPool); 211 } 212 213 214 /** 215 * Sets that target versions of the program classes. 216 */ 217 private void target() throws IOException 218 { 219 if (configuration.verbose) 220 { 221 System.out.println("Setting target versions..."); 222 } 223 224 new Targeter(configuration).execute(programClassPool); 225 } 226 227 228 /** 229 * Prints out classes and class members that are used as seeds in the 230 * shrinking and obfuscation steps. 231 */ 232 private void printSeeds() throws IOException 233 { 234 if (configuration.verbose) 235 { 236 System.out.println("Printing kept classes, fields, and methods..."); 237 } 238 239 // Check if we have at least some keep commands. 240 if (configuration.keep == null) 241 { 242 throw new IOException("You have to specify '-keep' options for the shrinking step."); 243 } 244 245 PrintStream ps = createPrintStream(configuration.printSeeds); 246 try 247 { 248 // Create a visitor for printing out the seeds. We're printing out 249 // the program elements that are preserved against shrinking, 250 // optimization, or obfuscation. 251 SimpleClassPrinter printer = new SimpleClassPrinter(false, ps); 252 ClassPoolVisitor classPoolvisitor = 253 ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep, 254 new ProgramClassFilter(printer), 255 new ProgramMemberFilter(printer), 256 true, 257 true, 258 true); 259 260 // Print out the seeds. 261 programClassPool.accept(classPoolvisitor); 262 libraryClassPool.accept(classPoolvisitor); 263 } 264 finally 265 { 266 closePrintStream(ps); 267 } 268 } 269 270 271 /** 272 * Performs the shrinking step. 273 */ 274 private void shrink() throws IOException 275 { 276 if (configuration.verbose) 277 { 278 System.out.println("Shrinking..."); 279 280 // We'll print out some explanation, if requested. 281 if (configuration.whyAreYouKeeping != null) 282 { 283 System.out.println("Explaining why classes and class members are being kept..."); 284 } 285 286 // We'll print out the usage, if requested. 287 if (configuration.printUsage != null) 288 { 289 System.out.println("Printing usage to [" + fileName(configuration.printUsage) + "]..."); 290 } 291 } 292 293 // Perform the actual shrinking. 294 programClassPool = 295 new Shrinker(configuration).execute(programClassPool, libraryClassPool); 296 } 297 298 299 /** 300 * Performs the subroutine inlining step. 301 */ 302 private void inlineSubroutines() 303 { 304 if (configuration.verbose) 305 { 306 System.out.println("Inlining subroutines..."); 307 } 308 309 // Perform the actual inlining. 310 new SubroutineInliner(configuration).execute(programClassPool); 311 } 312 313 314 /** 315 * Performs the optimization step. 316 */ 317 private boolean optimize() throws IOException 318 { 319 if (configuration.verbose) 320 { 321 System.out.println("Optimizing..."); 322 } 323 324 // Perform the actual optimization. 325 return new Optimizer(configuration).execute(programClassPool, libraryClassPool); 326 } 327 328 329 /** 330 * Performs the obfuscation step. 331 */ 332 private void obfuscate() throws IOException 333 { 334 if (configuration.verbose) 335 { 336 System.out.println("Obfuscating..."); 337 338 // We'll apply a mapping, if requested. 339 if (configuration.applyMapping != null) 340 { 341 System.out.println("Applying mapping [" + fileName(configuration.applyMapping) + "]"); 342 } 343 344 // We'll print out the mapping, if requested. 345 if (configuration.printMapping != null) 346 { 347 System.out.println("Printing mapping to [" + fileName(configuration.printMapping) + "]..."); 348 } 349 } 350 351 // Perform the actual obfuscation. 352 new Obfuscator(configuration).execute(programClassPool, libraryClassPool); 353 } 354 355 356 /** 357 * Performs the preverification step. 358 */ 359 private void preverify() 360 { 361 if (configuration.verbose) 362 { 363 System.out.println("Preverifying..."); 364 } 365 366 // Perform the actual preverification. 367 new Preverifier(configuration).execute(programClassPool); 368 } 369 370 371 /** 372 * Sorts the elements of all program classes. 373 */ 374 private void sortClassElements() 375 { 376 programClassPool.classesAccept(new ClassElementSorter()); 377 } 378 379 380 /** 381 * Writes the output class files. 382 */ 383 private void writeOutput() throws IOException 384 { 385 if (configuration.verbose) 386 { 387 System.out.println("Writing output..."); 388 } 389 390 // Write out the program class pool. 391 new OutputWriter(configuration).execute(programClassPool); 392 } 393 394 395 /** 396 * Prints out the contents of the program classes. 397 */ 398 private void dump() throws IOException 399 { 400 if (configuration.verbose) 401 { 402 System.out.println("Printing classes to [" + fileName(configuration.dump) + "]..."); 403 } 404 405 PrintStream ps = createPrintStream(configuration.dump); 406 try 407 { 408 programClassPool.classesAccept(new ClassPrinter(ps)); 409 } 410 finally 411 { 412 closePrintStream(ps); 413 } 414 } 415 416 417 /** 418 * Returns a print stream for the given file, or the standard output if 419 * the file name is empty. 420 */ 421 private PrintStream createPrintStream(File file) 422 throws FileNotFoundException 423 { 424 return isFile(file) ? 425 new PrintStream(new BufferedOutputStream(new FileOutputStream(file))) : 426 System.out; 427 } 428 429 430 /** 431 * Closes the given print stream, or closes it if is the standard output. 432 * @param printStream 433 */ 434 private void closePrintStream(PrintStream printStream) 435 { 436 if (printStream == System.out) 437 { 438 printStream.flush(); 439 } 440 else 441 { 442 printStream.close(); 443 } 444 } 445 446 447 /** 448 * Returns the absolute file name for the given file, or the standard output 449 * if the file name is empty. 450 */ 451 private String fileName(File file) 452 { 453 return isFile(file) ? 454 file.getAbsolutePath() : 455 "standard output"; 456 } 457 458 459 /** 460 * Returns whether the given file is actually a file, or just a placeholder 461 * for the standard output. 462 */ 463 private boolean isFile(File file) 464 { 465 return file.getPath().length() > 0; 466 } 467 468 469 /** 470 * The main method for ProGuard. 471 */ 472 public static void main(String[] args) 473 { 474 if (args.length == 0) 475 { 476 System.out.println(VERSION); 477 System.out.println("Usage: java proguard.ProGuard [options ...]"); 478 System.exit(1); 479 } 480 481 // Create the default options. 482 Configuration configuration = new Configuration(); 483 484 try 485 { 486 // Parse the options specified in the command line arguments. 487 ConfigurationParser parser = new ConfigurationParser(args); 488 489 try 490 { 491 parser.parse(configuration); 492 } 493 finally 494 { 495 parser.close(); 496 } 497 498 // Execute ProGuard with these options. 499 new ProGuard(configuration).execute(); 500 } 501 catch (Exception ex) 502 { 503 if (configuration.verbose) 504 { 505 // Print a verbose stack trace. 506 ex.printStackTrace(); 507 } 508 else 509 { 510 // Print just the stack trace message. 511 System.err.println("Error: "+ex.getMessage()); 512 } 513 514 System.exit(1); 515 } 516 517 System.exit(0); 518 } 519} 520