1
/* $Id: ProGuard.java,v 1.84 2004/12/11 16:35:23 eric Exp $
1
/* $Id: ProGuard.java,v 1.101 2005/10/22 11:53:39 eric Exp $
3
3
* ProGuard -- shrinking, optimization, and obfuscation of Java class files.
5
* Copyright (c) 2002-2004 Eric Lafortune (eric@graphics.cornell.edu)
5
* Copyright (c) 2002-2005 Eric Lafortune (eric@graphics.cornell.edu)
7
7
* This program is free software; you can redistribute it and/or modify it
8
8
* under the terms of the GNU General Public License as published by the Free
9
9
* Software Foundation; either version 2 of the License, or (at your option)
10
10
* any later version.
12
13
* This program is distributed in the hope that it will be useful, but WITHOUT
13
14
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14
15
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
23
import proguard.classfile.*;
24
import proguard.classfile.attribute.*;
25
import proguard.classfile.editor.*;
26
import proguard.classfile.instruction.*;
24
import proguard.classfile.ClassPool;
25
import proguard.classfile.attribute.AllAttrInfoVisitor;
26
import proguard.classfile.editor.ConstantPoolSorter;
27
import proguard.classfile.instruction.AllInstructionVisitor;
27
28
import proguard.classfile.util.*;
28
29
import proguard.classfile.visitor.*;
29
30
import proguard.io.*;
30
import proguard.obfuscate.*;
31
import proguard.optimize.*;
32
import proguard.optimize.evaluation.*;
33
import proguard.optimize.peephole.*;
34
import proguard.shrink.*;
31
import proguard.obfuscate.Obfuscator;
32
import proguard.optimize.Optimizer;
33
import proguard.shrink.Shrinker;
34
import proguard.util.ClassNameListMatcher;
40
40
* Tool for shrinking, optimizing, and obfuscating Java class files.
275
287
libraryClassPool.classFilesAccept(classFileHierarchyInitializer2);
277
// Initialize the other class references.
289
// Initialize the Class.forName and .class references.
290
ClassFileClassForNameReferenceInitializer classFileClassForNameReferenceInitializer =
291
new ClassFileClassForNameReferenceInitializer(programClassPool,
294
createNoteExceptionMatcher(configuration.keep));
296
programClassPool.classFilesAccept(
297
new AllMethodVisitor(
298
new AllAttrInfoVisitor(
299
new AllInstructionVisitor(classFileClassForNameReferenceInitializer))));
301
// Initialize the class references from program class members and attributes.
278
302
ClassFileReferenceInitializer classFileReferenceInitializer =
279
303
new ClassFileReferenceInitializer(programClassPool,
280
304
libraryClassPool,
284
307
programClassPool.classFilesAccept(classFileReferenceInitializer);
286
int noteCount = classFileReferenceInitializer.getNoteCount();
309
// Reinitialize the library class pool with only those library classes
310
// whose hierarchies are referenced by the program classes.
311
ClassPool newLibraryClassPool = new ClassPool();
312
programClassPool.classFilesAccept(
313
new AllCpInfoVisitor(
314
new ReferencedClassFileVisitor(
315
new LibraryClassFileFilter(
316
new ClassFileHierarchyTraveler(true, true, true, false,
317
new LibraryClassFileFilter(
318
new ClassPoolFiller(newLibraryClassPool, false)))))));
320
libraryClassPool = newLibraryClassPool;
322
// Initialize the class references from library class members.
323
ClassFileReferenceInitializer classFileReferenceInitializer2 =
324
new ClassFileReferenceInitializer(programClassPool,
328
libraryClassPool.classFilesAccept(classFileReferenceInitializer2);
330
int noteCount = classFileClassForNameReferenceInitializer.getNoteCount();
287
331
if (noteCount > 0)
289
333
System.err.println("Note: there were " + noteCount +
322
366
// Discard unused library classes.
323
367
if (configuration.verbose)
325
System.out.println("Removing unused library classes...");
326
System.out.println(" Original number of library classes: " + libraryClassPool.size());
369
System.out.println("Removed unused library classes...");
370
System.out.println(" Original number of library classes: " + originalLibraryClassPoolSize);
371
System.out.println(" Final number of library classes: " + libraryClassPool.size());
329
// Reinitialize the library class pool with only those library classes
330
// whose hierarchies are referenced by the program classes.
331
ClassPool newLibraryClassPool = new ClassPool();
332
programClassPool.classFilesAccept(
333
new AllCpInfoVisitor(
334
new ReferencedClassFileVisitor(
335
new LibraryClassFileFilter(
336
new ClassFileHierarchyTraveler(true, true, true, false,
337
new LibraryClassFileFilter(
338
new ClassPoolFiller(newLibraryClassPool, false)))))));
340
libraryClassPool = newLibraryClassPool;
342
if (configuration.verbose)
377
* Extracts a list of exceptions for which not to print notes, from the
378
* keep configuration.
380
private ClassNameListMatcher createNoteExceptionMatcher(List noteExceptions)
382
if (noteExceptions != null)
344
System.out.println(" Final number of library classes: " + libraryClassPool.size());
384
List noteExceptionNames = new ArrayList(noteExceptions.size());
385
for (int index = 0; index < noteExceptions.size(); index++)
387
ClassSpecification classSpecification = (ClassSpecification)noteExceptions.get(index);
388
if (classSpecification.markClassFiles)
390
// If the class itself is being kept, it's ok.
391
String className = classSpecification.className;
392
if (className != null)
394
noteExceptionNames.add(className);
397
// If all of its extensions are being kept, it's ok too.
398
String extendsClassName = classSpecification.extendsClassName;
399
if (extendsClassName != null)
401
noteExceptionNames.add(extendsClassName);
406
if (noteExceptionNames.size() > 0)
408
return new ClassNameListMatcher(noteExceptionNames);
394
461
if (configuration.verbose)
396
463
System.out.println("Shrinking...");
399
// Check if we have at least some keep commands.
400
if (configuration.keep == null)
402
throw new IOException("You have to specify '-keep' options for the shrinking step.");
405
// Clean up any old visitor info.
406
ClassFileCleaner classFileCleaner = new ClassFileCleaner();
407
programClassPool.classFilesAccept(classFileCleaner);
408
libraryClassPool.classFilesAccept(classFileCleaner);
410
// Create a visitor for marking the seeds.
411
UsageMarker usageMarker = new UsageMarker();
412
ClassPoolVisitor classPoolvisitor =
413
ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
417
programClassPool.accept(classPoolvisitor);
418
libraryClassPool.accept(classPoolvisitor);
420
// Mark interfaces that have to be kept.
421
programClassPool.classFilesAccept(new InterfaceUsageMarker());
423
// Mark the inner class information that has to be kept.
424
programClassPool.classFilesAccept(new InnerUsageMarker());
426
if (configuration.printUsage != null)
428
if (configuration.verbose)
465
// We'll print out some explanation, if requested.
466
if (configuration.whyAreYouKeeping != null)
468
System.out.println("Explaining why classes and class members are being kept...");
471
// We'll print out the usage, if requested.
472
if (configuration.printUsage != null)
430
474
System.out.println("Printing usage" +
431
(configuration.printUsage.length() > 0 ?
432
" to [" + configuration.printUsage + "]" :
475
(isFile(configuration.printUsage) ?
476
" to [" + configuration.printUsage.getAbsolutePath() + "]" :
436
PrintStream ps = configuration.printUsage.length() > 0 ?
437
new PrintStream(new BufferedOutputStream(new FileOutputStream(configuration.printUsage))) :
440
// Print out items that will be removed.
441
programClassPool.classFilesAcceptAlphabetically(new UsagePrinter(true, ps));
443
if (ps != System.out)
449
// Discard unused program classes.
450
if (configuration.verbose)
452
System.out.println("Removing unused program classes and class elements...");
453
System.out.println(" Original number of program classes: " + programClassPool.size());
456
ClassPool newProgramClassPool = new ClassPool();
457
programClassPool.classFilesAccept(
458
new UsedClassFileFilter(
459
new MultiClassFileVisitor(
460
new ClassFileVisitor[] {
461
new ClassFileShrinker(1024),
462
new ClassPoolFiller(newProgramClassPool, false)
464
programClassPool = newProgramClassPool;
466
if (configuration.verbose)
468
System.out.println(" Final number of program classes: " + programClassPool.size());
481
// Check if we have at least some keep commands.
482
if (configuration.keep == null)
484
throw new IOException("You have to specify '-keep' options for the shrinking step.");
487
int originalProgramClassPoolSize = programClassPool.size();
489
// Perform the actual shrinking.
490
programClassPool = new Shrinker(configuration).execute(programClassPool, libraryClassPool);
471
492
// Check if we have at least some output class files.
472
if (programClassPool.size() == 0)
493
int newProgramClassPoolSize = programClassPool.size();
494
if (newProgramClassPoolSize == 0)
474
496
throw new IOException("The output jar is empty. Did you specify the proper '-keep' options?");
499
if (configuration.verbose)
501
System.out.println("Removed unused program classes and class elements...");
502
System.out.println(" Original number of program classes: " + originalProgramClassPoolSize);
503
System.out.println(" Final number of program classes: " + newProgramClassPoolSize);
486
515
System.out.println("Optimizing...");
489
// Clean up any old visitor info.
490
ClassFileCleaner classFileCleaner = new ClassFileCleaner();
491
programClassPool.classFilesAccept(classFileCleaner);
492
libraryClassPool.classFilesAccept(classFileCleaner);
494
518
// Check if we have at least some keep commands.
495
if (configuration.keep == null)
519
if (configuration.keep == null &&
520
configuration.keepNames == null &&
521
configuration.applyMapping == null &&
522
configuration.printMapping == null)
497
524
throw new IOException("You have to specify '-keep' options for the optimization step.");
500
// Create a visitor for marking the seeds.
501
KeepMarker keepMarker = new KeepMarker();
502
ClassPoolVisitor classPoolvisitor =
503
ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
508
programClassPool.accept(classPoolvisitor);
509
libraryClassPool.accept(classPoolvisitor);
511
// Make class files and methods final, as far as possible.
512
programClassPool.classFilesAccept(new ClassFileFinalizer());
514
// Mark all fields that are write-only.
515
programClassPool.classFilesAccept(
516
new AllMethodVisitor(
517
new AllAttrInfoVisitor(
518
new AllInstructionVisitor(
519
new WriteOnlyFieldMarker()))));
521
if (configuration.assumeNoSideEffects != null)
523
// Create a visitor for marking methods that don't have any side effects.
524
NoSideEffectMethodMarker noSideEffectMethodMarker = new NoSideEffectMethodMarker();
525
ClassPoolVisitor noClassPoolvisitor =
526
ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.assumeNoSideEffects,
528
noSideEffectMethodMarker);
531
programClassPool.accept(noClassPoolvisitor);
532
libraryClassPool.accept(noClassPoolvisitor);
535
// Mark all methods that have side effects.
536
programClassPool.accept(new SideEffectMethodMarker());
538
// Mark all interfaces that have single implementations.
539
programClassPool.classFilesAccept(new SingleImplementationMarker(configuration.allowAccessModification));
541
// Inline interfaces with single implementations.
542
// First update the references to classes and class members.
543
// Then update the class member descriptors.
544
programClassPool.classFilesAccept(new AllMethodVisitor(
545
new AllAttrInfoVisitor(
546
new SingleImplementationInliner())));
547
programClassPool.classFilesAccept(new AllMemberInfoVisitor(
548
new SingleImplementationInliner()));
550
// Perform partial evaluation.
551
programClassPool.classFilesAccept(new AllMethodVisitor(
552
new PartialEvaluator()));
554
// Create a branch target marker and a code attribute editor that can
555
// be reused for all code attributes.
556
BranchTargetFinder branchTargetFinder = new BranchTargetFinder(1024);
557
CodeAttrInfoEditor codeAttrInfoEditor = new CodeAttrInfoEditor(1024);
559
// Visit all code attributes.
560
// First let the branch marker mark all branch targets.
561
// Then perform peephole optimisations on the instructions:
562
// - Remove push/pop instruction pairs.
563
// - Remove load/store instruction pairs.
564
// - Replace store/load instruction pairs by dup/store instructions.
565
// - Replace branches to return instructions by return instructions.
566
// - Remove nop instructions.
567
// - Inline simple getters and setters.
568
// Finally apply all changes to the code.
569
programClassPool.classFilesAccept(
570
new AllMethodVisitor(
571
new AllAttrInfoVisitor(
572
new MultiAttrInfoVisitor(
573
new AttrInfoVisitor[]
576
new CodeAttrInfoEditorResetter(codeAttrInfoEditor),
577
new AllInstructionVisitor(
578
new MultiInstructionVisitor(
579
new InstructionVisitor[]
581
new PushPopRemover(branchTargetFinder, codeAttrInfoEditor),
582
new LoadStoreRemover(branchTargetFinder, codeAttrInfoEditor),
583
new StoreLoadReplacer(branchTargetFinder, codeAttrInfoEditor),
584
new GotoReturnReplacer(codeAttrInfoEditor),
585
new NopRemover(codeAttrInfoEditor),
586
new GetterSetterInliner(codeAttrInfoEditor, configuration.allowAccessModification),
527
// Perform the actual optimization.
528
new Optimizer(configuration).execute(programClassPool, libraryClassPool);
598
537
if (configuration.verbose)
600
539
System.out.println("Obfuscating...");
603
// Check if we have at least some keep commands.
604
if (configuration.keep == null &&
605
configuration.keepNames == null)
607
throw new IOException("You have to specify '-keep' options for the obfuscation step.");
610
// Clean up any old visitor info.
611
ClassFileCleaner classFileCleaner = new ClassFileCleaner();
612
programClassPool.classFilesAccept(classFileCleaner);
613
libraryClassPool.classFilesAccept(classFileCleaner);
615
// Link all class members that should get the same names.
616
programClassPool.classFilesAccept(new BottomClassFileFilter(
617
new MemberInfoLinker()));
619
// Create a visitor for marking the seeds.
620
NameMarker nameMarker = new NameMarker();
621
ClassPoolVisitor classPoolvisitor =
622
new MultiClassPoolVisitor(new ClassPoolVisitor[]
624
ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
627
ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keepNames,
633
programClassPool.accept(classPoolvisitor);
634
libraryClassPool.accept(classPoolvisitor);
636
// Apply the mapping, if one has been specified.
637
if (configuration.applyMapping != null)
639
if (configuration.verbose)
641
System.out.println("Applying mapping [" + configuration.applyMapping + "]");
644
MappingReader reader = new MappingReader(configuration.applyMapping);
645
MappingProcessor keeper =
646
new MultiMappingProcessor(new MappingProcessor[]
648
new MappingKeeper(programClassPool),
649
new MappingKeeper(libraryClassPool),
655
// Mark attributes that have to be kept.
656
AttributeUsageMarker attributeUsageMarker = new AttributeUsageMarker();
657
if (configuration.keepAttributes != null)
659
if (configuration.keepAttributes.size() != 0)
661
attributeUsageMarker.setKeepAttributes(configuration.keepAttributes);
665
attributeUsageMarker.setKeepAllAttributes();
668
programClassPool.classFilesAccept(attributeUsageMarker);
670
// Remove the attributes that can be discarded.
671
programClassPool.classFilesAccept(new AttributeShrinker());
673
if (configuration.verbose)
675
System.out.println("Renaming program classes and class elements...");
678
// Come up with new names for all class files.
679
programClassPool.classFilesAccept(new ClassFileObfuscator(programClassPool,
680
configuration.defaultPackage,
681
configuration.useMixedCaseClassNames));
683
// Come up with new names for all class members.
684
programClassPool.classFilesAccept(new BottomClassFileFilter(
685
new MemberInfoObfuscator(configuration.overloadAggressively,
686
configuration.obfuscationDictionary)));
688
// Print out the mapping, if requested.
689
if (configuration.printMapping != null)
691
if (configuration.verbose)
541
// We'll apply a mapping, if requested.
542
if (configuration.applyMapping != null)
544
System.out.println("Applying mapping [" + configuration.applyMapping.getAbsolutePath() + "]");
547
// We'll print out the mapping, if requested.
548
if (configuration.printMapping != null)
693
550
System.out.println("Printing mapping" +
694
(configuration.printMapping.length() > 0 ?
695
" to [" + configuration.printMapping + "]" :
551
(isFile(configuration.printMapping) ?
552
" to [" + configuration.printMapping.getAbsolutePath() + "]" :
699
PrintStream ps = configuration.printMapping.length() > 0 ?
700
new PrintStream(new BufferedOutputStream(new FileOutputStream(configuration.printMapping))) :
703
// Print out items that will be removed.
704
programClassPool.classFilesAcceptAlphabetically(new MappingPrinter(ps));
706
if (ps != System.out)
712
// Actually apply these new names.
713
programClassPool.classFilesAccept(new ClassFileRenamer(configuration.defaultPackage != null,
714
configuration.newSourceFileAttribute));
716
// Mark NameAndType constant pool entries that have to be kept
717
// and remove the other ones.
718
programClassPool.classFilesAccept(new NameAndTypeUsageMarker());
719
programClassPool.classFilesAccept(new NameAndTypeShrinker(1024));
721
// Mark Utf8 constant pool entries that have to be kept
722
// and remove the other ones.
723
programClassPool.classFilesAccept(new Utf8UsageMarker());
724
programClassPool.classFilesAccept(new Utf8Shrinker(1024));
557
// Perform the actual obfuscation.
558
new Obfuscator(configuration).execute(programClassPool, libraryClassPool);