001: /*
002: * ProGuard -- shrinking, optimization, obfuscation, and preverification
003: * of Java bytecode.
004: *
005: * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
006: *
007: * This program is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU General Public License as published by the Free
009: * Software Foundation; either version 2 of the License, or (at your option)
010: * any later version.
011: *
012: *
013: * This program is distributed in the hope that it will be useful, but WITHOUT
014: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
015: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
016: * more details.
017: *
018: * You should have received a copy of the GNU General Public License along
019: * with this program; if not, write to the Free Software Foundation, Inc.,
020: * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021: */
022: package proguard.obfuscate;
023:
024: import proguard.*;
025: import proguard.classfile.*;
026: import proguard.classfile.attribute.visitor.*;
027: import proguard.classfile.constant.visitor.AllConstantVisitor;
028: import proguard.classfile.editor.*;
029: import proguard.classfile.util.*;
030: import proguard.classfile.visitor.*;
031: import proguard.util.*;
032:
033: import java.io.*;
034: import java.util.*;
035:
036: /**
037: * This class can perform obfuscation of class pools according to a given
038: * specification.
039: *
040: * @author Eric Lafortune
041: */
042: public class Obfuscator {
043: private final Configuration configuration;
044:
045: /**
046: * Creates a new Obfuscator.
047: */
048: public Obfuscator(Configuration configuration) {
049: this .configuration = configuration;
050: }
051:
052: /**
053: * Performs obfuscation of the given program class pool.
054: */
055: public void execute(ClassPool programClassPool,
056: ClassPool libraryClassPool) throws IOException {
057: // Check if we have at least some keep commands.
058: if (configuration.keep == null
059: && configuration.applyMapping == null
060: && configuration.printMapping == null) {
061: throw new IOException(
062: "You have to specify '-keep' options for the obfuscation step.");
063: }
064:
065: // Clean up any old visitor info.
066: programClassPool.classesAccept(new ClassCleaner());
067: libraryClassPool.classesAccept(new ClassCleaner());
068:
069: // If the class member names have to correspond globally,
070: // link all class members in all classes, otherwise
071: // link all non-private methods in all class hierarchies.
072: ClassVisitor memberInfoLinker = configuration.useUniqueClassMemberNames ? (ClassVisitor) new AllMemberVisitor(
073: new MethodLinker())
074: : (ClassVisitor) new BottomClassFilter(
075: new MethodLinker());
076:
077: programClassPool.classesAccept(memberInfoLinker);
078: libraryClassPool.classesAccept(memberInfoLinker);
079:
080: // Create a visitor for marking the seeds.
081: NameMarker nameMarker = new NameMarker();
082: ClassPoolVisitor classPoolvisitor = ClassSpecificationVisitorFactory
083: .createClassPoolVisitor(configuration.keep, nameMarker,
084: nameMarker, false, false, true);
085: // Mark the seeds.
086: programClassPool.accept(classPoolvisitor);
087: libraryClassPool.accept(classPoolvisitor);
088:
089: // All library classes and library class members keep their names.
090: libraryClassPool.classesAccept(nameMarker);
091: libraryClassPool
092: .classesAccept(new AllMemberVisitor(nameMarker));
093:
094: // Apply the mapping, if one has been specified. The mapping can
095: // override the names of library classes and of library class members.
096: if (configuration.applyMapping != null) {
097: WarningPrinter warningPrinter = configuration.warn ? new WarningPrinter(
098: System.err)
099: : null;
100:
101: MappingReader reader = new MappingReader(
102: configuration.applyMapping);
103:
104: MappingProcessor keeper = new MultiMappingProcessor(
105: new MappingProcessor[] {
106: new MappingKeeper(programClassPool,
107: warningPrinter),
108: new MappingKeeper(libraryClassPool, null), });
109:
110: reader.pump(keeper);
111:
112: if (warningPrinter != null) {
113: // Print out a summary of the warnings if necessary.
114: int mappingWarningCount = warningPrinter
115: .getWarningCount();
116: if (mappingWarningCount > 0) {
117: System.err
118: .println("Warning: there were "
119: + mappingWarningCount
120: + " kept classes and class members that were remapped anyway.");
121: System.err
122: .println(" You should adapt your configuration or edit the mapping file.");
123:
124: if (!configuration.ignoreWarnings) {
125: System.err
126: .println(" If you are sure this remapping won't hurt,");
127: System.err
128: .println(" you could try your luck using the '-ignorewarnings' option.");
129: throw new IOException(
130: "Please correct the above warnings first.");
131: }
132: }
133: }
134: }
135:
136: // Mark attributes that have to be kept.
137: AttributeUsageMarker requiredAttributeUsageMarker = new AttributeUsageMarker();
138:
139: AttributeVisitor optionalAttributeUsageMarker = configuration.keepAttributes == null ? null
140: : configuration.keepAttributes.size() == 0 ? (AttributeVisitor) requiredAttributeUsageMarker
141: : (AttributeVisitor) new AttributeNameFilter(
142: new ListParser(new NameParser())
143: .parse(configuration.keepAttributes),
144: requiredAttributeUsageMarker);
145:
146: programClassPool.classesAccept(new AllAttributeVisitor(true,
147: new RequiredAttributeFilter(
148: requiredAttributeUsageMarker,
149: optionalAttributeUsageMarker)));
150:
151: // Remove the attributes that can be discarded.
152: programClassPool.classesAccept(new AttributeShrinker());
153:
154: // Come up with new names for all classes.
155: programClassPool.classesAccept(new ClassObfuscator(
156: programClassPool, configuration.useMixedCaseClassNames,
157: configuration.flattenPackageHierarchy,
158: configuration.repackageClasses,
159: configuration.allowAccessModification));
160:
161: // Come up with new names for all class members.
162: NameFactory nameFactory = new SimpleNameFactory();
163:
164: if (configuration.obfuscationDictionary != null) {
165: nameFactory = new DictionaryNameFactory(
166: configuration.obfuscationDictionary, nameFactory);
167: }
168:
169: WarningPrinter warningPrinter = configuration.warn ? new WarningPrinter(
170: System.err)
171: : null;
172:
173: // Maintain a map of names to avoid [descriptor - new name - old name].
174: Map descriptorMap = new HashMap();
175:
176: // Do the class member names have to be globally unique?
177: if (configuration.useUniqueClassMemberNames) {
178: // Collect all member names in all classes.
179: programClassPool.classesAccept(new AllMemberVisitor(
180: new MemberNameCollector(
181: configuration.overloadAggressively,
182: descriptorMap)));
183:
184: // Assign new names to all members in all classes.
185: programClassPool.classesAccept(new AllMemberVisitor(
186: new MemberObfuscator(
187: configuration.overloadAggressively,
188: nameFactory, descriptorMap)));
189: } else {
190: // Come up with new names for all non-private class members.
191: programClassPool
192: .classesAccept(new MultiClassVisitor(
193: new ClassVisitor[] {
194: // Collect all private member names in this class and down
195: // the hierarchy.
196: new ClassHierarchyTraveler(
197: true,
198: false,
199: false,
200: true,
201: new AllMemberVisitor(
202: new MemberAccessFilter(
203: ClassConstants.INTERNAL_ACC_PRIVATE,
204: 0,
205: new MemberNameCollector(
206: configuration.overloadAggressively,
207: descriptorMap)))),
208:
209: // Collect all non-private member names anywhere in the hierarchy.
210: new ClassHierarchyTraveler(
211: true,
212: true,
213: true,
214: true,
215: new AllMemberVisitor(
216: new MemberAccessFilter(
217: 0,
218: ClassConstants.INTERNAL_ACC_PRIVATE,
219: new MemberNameCollector(
220: configuration.overloadAggressively,
221: descriptorMap)))),
222:
223: // Assign new names to all non-private members in this class.
224: new AllMemberVisitor(
225: new MemberAccessFilter(
226: 0,
227: ClassConstants.INTERNAL_ACC_PRIVATE,
228: new MemberObfuscator(
229: configuration.overloadAggressively,
230: nameFactory,
231: descriptorMap))),
232:
233: // Clear the collected names.
234: new MapCleaner(descriptorMap) }));
235:
236: // Come up with new names for all private class members.
237: programClassPool
238: .classesAccept(new MultiClassVisitor(
239: new ClassVisitor[] {
240: // Collect all member names in this class.
241: new AllMemberVisitor(
242: new MemberNameCollector(
243: configuration.overloadAggressively,
244: descriptorMap)),
245:
246: // Collect all non-private member names higher up the hierarchy.
247: new ClassHierarchyTraveler(
248: false,
249: true,
250: true,
251: false,
252: new AllMemberVisitor(
253: new MemberAccessFilter(
254: 0,
255: ClassConstants.INTERNAL_ACC_PRIVATE,
256: new MemberNameCollector(
257: configuration.overloadAggressively,
258: descriptorMap)))),
259:
260: // Assign new names to all private members in this class.
261: new AllMemberVisitor(
262: new MemberAccessFilter(
263: ClassConstants.INTERNAL_ACC_PRIVATE,
264: 0,
265: new MemberObfuscator(
266: configuration.overloadAggressively,
267: nameFactory,
268: descriptorMap))),
269:
270: // Clear the collected names.
271: new MapCleaner(descriptorMap) }));
272: }
273:
274: // Some class members may have ended up with conflicting names.
275: // Come up with new, globally unique names for them.
276: NameFactory specialNameFactory = new SpecialNameFactory(
277: new SimpleNameFactory());
278:
279: // Collect a map of special names to avoid
280: // [descriptor - new name - old name].
281: Map specialDescriptorMap = new HashMap();
282:
283: programClassPool.classesAccept(new AllMemberVisitor(
284: new MemberSpecialNameFilter(new MemberNameCollector(
285: configuration.overloadAggressively,
286: specialDescriptorMap))));
287:
288: libraryClassPool.classesAccept(new AllMemberVisitor(
289: new MemberSpecialNameFilter(new MemberNameCollector(
290: configuration.overloadAggressively,
291: specialDescriptorMap))));
292:
293: // Replace conflicting non-private member names with special names.
294: programClassPool
295: .classesAccept(new MultiClassVisitor(
296: new ClassVisitor[] {
297: // Collect all private member names in this class and down
298: // the hierarchy.
299: new ClassHierarchyTraveler(
300: true,
301: false,
302: false,
303: true,
304: new AllMemberVisitor(
305: new MemberAccessFilter(
306: ClassConstants.INTERNAL_ACC_PRIVATE,
307: 0,
308: new MemberNameCollector(
309: configuration.overloadAggressively,
310: descriptorMap)))),
311:
312: // Collect all non-private member names in this class and
313: // higher up the hierarchy.
314: new ClassHierarchyTraveler(
315: true,
316: true,
317: true,
318: false,
319: new AllMemberVisitor(
320: new MemberAccessFilter(
321: 0,
322: ClassConstants.INTERNAL_ACC_PRIVATE,
323: new MemberNameCollector(
324: configuration.overloadAggressively,
325: descriptorMap)))),
326:
327: // Assign new names to all conflicting non-private members
328: // in this class and higher up the hierarchy.
329: new ClassHierarchyTraveler(
330: true,
331: true,
332: true,
333: false,
334: new AllMemberVisitor(
335: new MemberAccessFilter(
336: 0,
337: ClassConstants.INTERNAL_ACC_PRIVATE,
338: new MemberNameConflictFixer(
339: configuration.overloadAggressively,
340: descriptorMap,
341: warningPrinter,
342: new MemberObfuscator(
343: configuration.overloadAggressively,
344: specialNameFactory,
345: specialDescriptorMap))))),
346:
347: // Clear the collected names.
348: new MapCleaner(descriptorMap) }));
349:
350: // Replace conflicting private member names with special names.
351: // This is only possible if those names were kept or mapped.
352: programClassPool
353: .classesAccept(new MultiClassVisitor(
354: new ClassVisitor[] {
355: // Collect all member names in this class.
356: new AllMemberVisitor(
357: new MemberNameCollector(
358: configuration.overloadAggressively,
359: descriptorMap)),
360:
361: // Collect all non-private member names higher up the hierarchy.
362: new ClassHierarchyTraveler(
363: false,
364: true,
365: true,
366: false,
367: new AllMemberVisitor(
368: new MemberAccessFilter(
369: 0,
370: ClassConstants.INTERNAL_ACC_PRIVATE,
371: new MemberNameCollector(
372: configuration.overloadAggressively,
373: descriptorMap)))),
374:
375: // Assign new names to all conflicting private members in this
376: // class.
377: new AllMemberVisitor(
378: new MemberAccessFilter(
379: ClassConstants.INTERNAL_ACC_PRIVATE,
380: 0,
381: new MemberNameConflictFixer(
382: configuration.overloadAggressively,
383: descriptorMap,
384: warningPrinter,
385: new MemberObfuscator(
386: configuration.overloadAggressively,
387: specialNameFactory,
388: specialDescriptorMap)))),
389:
390: // Clear the collected names.
391: new MapCleaner(descriptorMap) }));
392:
393: // Print out any warnings about member name conflicts.
394: if (warningPrinter != null) {
395: int warningCount = warningPrinter.getWarningCount();
396: if (warningCount > 0) {
397: System.err.println("Warning: there were "
398: + warningCount
399: + " conflicting class member name mappings.");
400: System.err
401: .println(" Your configuration may be inconsistent.");
402:
403: if (!configuration.ignoreWarnings) {
404: System.err
405: .println(" If you are sure the conflicts are harmless,");
406: System.err
407: .println(" you could try your luck using the '-ignorewarnings' option.");
408: throw new IOException(
409: "Please correct the above warnings first.");
410: }
411: }
412: }
413:
414: // Print out the mapping, if requested.
415: if (configuration.printMapping != null) {
416: PrintStream ps = isFile(configuration.printMapping) ? new PrintStream(
417: new BufferedOutputStream(new FileOutputStream(
418: configuration.printMapping)))
419: : System.out;
420:
421: // Print out items that will be removed.
422: programClassPool
423: .classesAcceptAlphabetically(new MappingPrinter(ps));
424:
425: if (ps != System.out) {
426: ps.close();
427: }
428: }
429:
430: // Actually apply the new names.
431: programClassPool.classesAccept(new ClassRenamer());
432: libraryClassPool.classesAccept(new ClassRenamer());
433:
434: // Update all references to these new names.
435: programClassPool.classesAccept(new ClassReferenceFixer(false));
436: libraryClassPool.classesAccept(new ClassReferenceFixer(false));
437: programClassPool.classesAccept(new MemberReferenceFixer());
438:
439: // Make package visible elements public or protected, if obfuscated
440: // classes are being repackaged aggressively.
441: if (configuration.repackageClasses != null
442: && configuration.allowAccessModification) {
443: programClassPool.classesAccept(new AllConstantVisitor(
444: new ClassOpener()));
445: }
446:
447: // Rename the source file attributes, if requested.
448: if (configuration.newSourceFileAttribute != null) {
449: programClassPool.classesAccept(new SourceFileRenamer(
450: configuration.newSourceFileAttribute));
451: }
452:
453: // Mark NameAndType constant pool entries that have to be kept
454: // and remove the other ones.
455: programClassPool.classesAccept(new NameAndTypeUsageMarker());
456: programClassPool.classesAccept(new NameAndTypeShrinker());
457:
458: // Mark Utf8 constant pool entries that have to be kept
459: // and remove the other ones.
460: programClassPool.classesAccept(new Utf8UsageMarker());
461: programClassPool.classesAccept(new Utf8Shrinker());
462: }
463:
464: /**
465: * Returns whether the given file is actually a file, or just a placeholder
466: * for the standard output.
467: */
468: private boolean isFile(File file) {
469: return file.getPath().length() > 0;
470: }
471: }
|