• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
5 // for details. All rights reserved. Use of this source code is governed by a
6 // BSD-style license that can be found in the LICENSE file.
7 package com.android.tools.r8.graph;
8 
9 import com.android.tools.r8.naming.ClassNameMapper;
10 import com.android.tools.r8.utils.ClasspathClassCollection;
11 import com.android.tools.r8.utils.InternalOptions;
12 import com.android.tools.r8.utils.LibraryClassCollection;
13 import com.android.tools.r8.utils.ProgramClassCollection;
14 import com.android.tools.r8.utils.StringUtils;
15 import com.android.tools.r8.utils.Timing;
16 import com.google.common.collect.ImmutableSet;
17 import com.google.common.collect.Sets;
18 import java.io.ByteArrayOutputStream;
19 import java.io.IOException;
20 import java.io.PrintStream;
21 import java.nio.charset.StandardCharsets;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.IdentityHashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 
33 public class DexApplication {
34 
35   // Maps type into class, may be used concurrently.
36   private ProgramClassCollection programClasses;
37   private ClasspathClassCollection classpathClasses;
38   private LibraryClassCollection libraryClasses;
39 
40   public final ImmutableSet<DexType> mainDexList;
41 
42   private final ClassNameMapper proguardMap;
43 
44   public final Timing timing;
45 
46   public final DexItemFactory dexItemFactory;
47 
48   // Information on the lexicographically largest string referenced from code.
49   public final DexString highestSortingString;
50 
51   /** Constructor should only be invoked by the DexApplication.Builder. */
DexApplication( ClassNameMapper proguardMap, ProgramClassCollection programClasses, ClasspathClassCollection classpathClasses, LibraryClassCollection libraryClasses, ImmutableSet<DexType> mainDexList, DexItemFactory dexItemFactory, DexString highestSortingString, Timing timing)52   private DexApplication(
53       ClassNameMapper proguardMap,
54       ProgramClassCollection programClasses,
55       ClasspathClassCollection classpathClasses,
56       LibraryClassCollection libraryClasses,
57       ImmutableSet<DexType> mainDexList,
58       DexItemFactory dexItemFactory,
59       DexString highestSortingString,
60       Timing timing) {
61     assert programClasses != null;
62     this.proguardMap = proguardMap;
63     this.programClasses = programClasses;
64     this.classpathClasses = classpathClasses;
65     this.libraryClasses = libraryClasses;
66     this.mainDexList = mainDexList;
67     this.dexItemFactory = dexItemFactory;
68     this.highestSortingString = highestSortingString;
69     this.timing = timing;
70   }
71 
72   /** Force load all classes and return type -> class map containing all the classes */
getFullClassMap()73   public Map<DexType, DexClass> getFullClassMap() {
74     return forceLoadAllClasses();
75   }
76 
77   // Reorder classes randomly. Note that the order of classes in program or library
78   // class collections should not matter for compilation of valid code and when running
79   // with assertions enabled we reorder the classes randomly to catch possible issues.
80   // Also note that the order may add to non-determinism in reporting errors for invalid
81   // code, but this non-determinism exists even with the same order of classes since we
82   // may process classes concurrently and fail-fast on the first error.
reorderClasses(List<T> classes)83   private <T> boolean reorderClasses(List<T> classes) {
84     Collections.shuffle(classes);
85     return true;
86   }
87 
classes()88   public List<DexProgramClass> classes() {
89     programClasses.forceLoad(type -> true);
90     List<DexProgramClass> classes = programClasses.getAllClasses();
91     assert reorderClasses(classes);
92     return classes;
93   }
94 
libraryClasses()95   public List<DexLibraryClass> libraryClasses() {
96     assert classpathClasses == null : "Operation is not supported.";
97     Map<DexType, DexClass> classMap = forceLoadAllClasses();
98     List<DexLibraryClass> classes = new ArrayList<>();
99     for (DexClass clazz : classMap.values()) {
100       if (clazz.isLibraryClass()) {
101         classes.add(clazz.asLibraryClass());
102       }
103     }
104     assert reorderClasses(classes);
105     return classes;
106   }
107 
forceLoadAllClasses()108   private Map<DexType, DexClass> forceLoadAllClasses() {
109     Map<DexType, DexClass> loaded = new IdentityHashMap<>();
110 
111     // program classes are supposed to be loaded, but force-loading them is no-op.
112     programClasses.forceLoad(type -> true);
113     programClasses.getAllClasses().forEach(clazz -> loaded.put(clazz.type, clazz));
114 
115     if (classpathClasses != null) {
116       classpathClasses.forceLoad(type -> !loaded.containsKey(type));
117       classpathClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
118     }
119 
120     if (libraryClasses != null) {
121       libraryClasses.forceLoad(type -> !loaded.containsKey(type));
122       libraryClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
123     }
124 
125     return loaded;
126   }
127 
definitionFor(DexType type)128   public DexClass definitionFor(DexType type) {
129     DexClass clazz = programClasses.get(type);
130     if (clazz == null && classpathClasses != null) {
131       clazz = classpathClasses.get(type);
132     }
133     if (clazz == null && libraryClasses != null) {
134       clazz = libraryClasses.get(type);
135     }
136     return clazz;
137   }
138 
programDefinitionFor(DexType type)139   public DexProgramClass programDefinitionFor(DexType type) {
140     DexClass clazz = programClasses.get(type);
141     return clazz == null ? null : clazz.asProgramClass();
142   }
143 
toString()144   public String toString() {
145     return "Application (" + programClasses + "; " + classpathClasses + "; " + libraryClasses + ")";
146   }
147 
getProguardMap()148   public ClassNameMapper getProguardMap() {
149     return proguardMap;
150   }
151 
disassemble(DexEncodedMethod method, ClassNameMapper naming, Path outputDir)152   private void disassemble(DexEncodedMethod method, ClassNameMapper naming, Path outputDir) {
153     if (method.getCode() != null) {
154       PrintStream ps = System.out;
155       try {
156         String clazzName;
157         String methodName;
158         if (naming != null) {
159           clazzName = naming.originalNameOf(method.method.holder);
160           methodName = naming.originalSignatureOf(method.method).toString();
161         } else {
162           clazzName = method.method.holder.toSourceString();
163           methodName = method.method.name.toString();
164         }
165         if (outputDir != null) {
166           Path directory = outputDir.resolve(clazzName.replace('.', '/'));
167           String name = methodName + ".dump";
168           if (name.length() > 200) {
169             name = StringUtils.computeMD5Hash(name);
170           }
171           Files.createDirectories(directory);
172           ps = new PrintStream(Files.newOutputStream(directory.resolve(name)));
173         }
174         ps.println("Bytecode for");
175         ps.println("Class: '" + clazzName + "'");
176         ps.println("Method: '" + methodName + "':");
177         ps.println(method.getCode().toString(method, naming));
178       } catch (IOException e) {
179         e.printStackTrace();
180       } finally {
181         if (outputDir != null) {
182           ps.flush();
183           ps.close();
184         }
185       }
186     }
187   }
188 
189   /**
190    * Write disassembly for the application code in the provided directory.
191    *
192    * <p>If no directory is provided everything is written to System.out.
193    */
disassemble(Path outputDir, InternalOptions options)194   public void disassemble(Path outputDir, InternalOptions options) {
195     for (DexProgramClass clazz : programClasses.getAllClasses()) {
196       for (DexEncodedMethod method : clazz.virtualMethods()) {
197         if (options.methodMatchesFilter(method)) {
198           disassemble(method, getProguardMap(), outputDir);
199         }
200       }
201       for (DexEncodedMethod method : clazz.directMethods()) {
202         if (options.methodMatchesFilter(method)) {
203           disassemble(method, getProguardMap(), outputDir);
204         }
205       }
206     }
207   }
208 
209   /** Return smali source for the application code. */
smali(InternalOptions options)210   public String smali(InternalOptions options) {
211     ByteArrayOutputStream os = new ByteArrayOutputStream();
212     PrintStream ps = new PrintStream(os);
213     smali(options, ps);
214     return new String(os.toByteArray(), StandardCharsets.UTF_8);
215   }
216 
writeClassHeader(DexClass clazz, PrintStream ps)217   private void writeClassHeader(DexClass clazz, PrintStream ps) {
218     StringBuilder builder = new StringBuilder();
219     builder.append(".class ");
220     builder.append(clazz.accessFlags.toSmaliString());
221     builder.append(" ");
222     builder.append(clazz.type.toSmaliString());
223     builder.append("\n\n");
224     if (clazz.type != dexItemFactory.objectType) {
225       builder.append(".super ");
226       builder.append(clazz.superType.toSmaliString());
227       builder.append("\n");
228       for (DexType iface : clazz.interfaces.values) {
229         builder.append(".implements ");
230         builder.append(iface.toSmaliString());
231         builder.append("\n");
232       }
233     }
234     ps.append(builder.toString());
235   }
236 
writeClassFooter(DexClass clazz, PrintStream ps)237   private void writeClassFooter(DexClass clazz, PrintStream ps) {
238     StringBuilder builder = new StringBuilder();
239     builder.append("# End of class ");
240     builder.append(clazz.type.toSmaliString());
241     builder.append("\n");
242     ps.append(builder.toString());
243   }
244 
245   /**
246    * Write smali source for the application code on the provided PrintStream.
247    */
smali(InternalOptions options, PrintStream ps)248   public void smali(InternalOptions options, PrintStream ps) {
249     List<DexProgramClass> classes = programClasses.getAllClasses();
250     classes.sort(Comparator.comparing(DexProgramClass::toSourceString));
251     boolean firstClass = true;
252     for (DexClass clazz : classes) {
253       boolean classHeaderWritten = false;
254       if (!options.hasMethodsFilter()) {
255         if (!firstClass) {
256           ps.append("\n");
257           firstClass = false;
258         }
259         writeClassHeader(clazz, ps);
260         classHeaderWritten = true;
261       }
262       for (DexEncodedMethod method : clazz.virtualMethods()) {
263         if (options.methodMatchesFilter(method)) {
264           if (!classHeaderWritten) {
265             if (!firstClass) {
266               ps.append("\n");
267               firstClass = false;
268             }
269             writeClassHeader(clazz, ps);
270             classHeaderWritten = true;
271           }
272           ps.append("\n");
273           ps.append(method.toSmaliString(getProguardMap()));
274         }
275       }
276       for (DexEncodedMethod method : clazz.directMethods()) {
277         if (options.methodMatchesFilter(method)) {
278           if (!classHeaderWritten) {
279             if (!firstClass) {
280               ps.append("\n");
281               firstClass = false;
282             }
283             writeClassHeader(clazz, ps);
284             classHeaderWritten = true;
285           }
286           ps.append("\n");
287           ps.append(method.toSmaliString(getProguardMap()));
288         }
289       }
290       if (classHeaderWritten) {
291         ps.append("\n");
292         writeClassFooter(clazz, ps);
293       }
294     }
295   }
296 
297   public static class Builder {
298     // We handle program class collection separately from classpath
299     // and library class collections. Since while we assume program
300     // class collection should always be fully loaded and thus fully
301     // represented by the map (making it easy, for example, adding
302     // new or removing existing classes), classpath and library
303     // collections will be considered monolithic collections.
304 
305     private final List<DexProgramClass> programClasses;
306     private ClasspathClassCollection classpathClasses;
307     private LibraryClassCollection libraryClasses;
308 
309     public final DexItemFactory dexItemFactory;
310     public ClassNameMapper proguardMap;
311     private final Timing timing;
312 
313     public DexString highestSortingString;
314     private final Set<DexType> mainDexList = Sets.newIdentityHashSet();
315 
Builder(DexItemFactory dexItemFactory, Timing timing)316     public Builder(DexItemFactory dexItemFactory, Timing timing) {
317       this.programClasses = new ArrayList<>();
318       this.dexItemFactory = dexItemFactory;
319       this.timing = timing;
320       this.classpathClasses = null;
321       this.libraryClasses = null;
322     }
323 
Builder(DexApplication application)324     public Builder(DexApplication application) {
325       programClasses = application.programClasses.getAllClasses();
326       classpathClasses = application.classpathClasses;
327       libraryClasses = application.libraryClasses;
328       proguardMap = application.proguardMap;
329       timing = application.timing;
330       highestSortingString = application.highestSortingString;
331       dexItemFactory = application.dexItemFactory;
332       mainDexList.addAll(application.mainDexList);
333     }
334 
setProguardMap(ClassNameMapper proguardMap)335     public synchronized Builder setProguardMap(ClassNameMapper proguardMap) {
336       assert this.proguardMap == null;
337       this.proguardMap = proguardMap;
338       return this;
339     }
340 
replaceProgramClasses(List<DexProgramClass> newProgramClasses)341     public synchronized Builder replaceProgramClasses(List<DexProgramClass> newProgramClasses) {
342       assert newProgramClasses != null;
343       this.programClasses.clear();
344       this.programClasses.addAll(newProgramClasses);
345       return this;
346     }
347 
setHighestSortingString(DexString value)348     public synchronized Builder setHighestSortingString(DexString value) {
349       highestSortingString = value;
350       return this;
351     }
352 
addProgramClass(DexProgramClass clazz)353     public synchronized Builder addProgramClass(DexProgramClass clazz) {
354       programClasses.add(clazz);
355       return this;
356     }
357 
setClasspathClassCollection(ClasspathClassCollection classes)358     public Builder setClasspathClassCollection(ClasspathClassCollection classes) {
359       this.classpathClasses = classes;
360       return this;
361     }
362 
setLibraryClassCollection(LibraryClassCollection classes)363     public Builder setLibraryClassCollection(LibraryClassCollection classes) {
364       this.libraryClasses = classes;
365       return this;
366     }
367 
addSynthesizedClass( DexProgramClass synthesizedClass, boolean addToMainDexList)368     public synchronized Builder addSynthesizedClass(
369         DexProgramClass synthesizedClass, boolean addToMainDexList) {
370       assert synthesizedClass.isProgramClass() : "All synthesized classes must be program classes";
371       addProgramClass(synthesizedClass);
372       if (addToMainDexList && !mainDexList.isEmpty()) {
373         mainDexList.add(synthesizedClass.type);
374       }
375       return this;
376     }
377 
getProgramClasses()378     public Collection<DexProgramClass> getProgramClasses() {
379       return programClasses;
380     }
381 
addToMainDexList(Collection<DexType> mainDexList)382     public Builder addToMainDexList(Collection<DexType> mainDexList) {
383       this.mainDexList.addAll(mainDexList);
384       return this;
385     }
386 
build()387     public DexApplication build() {
388       return new DexApplication(
389           proguardMap,
390           ProgramClassCollection.create(programClasses),
391           classpathClasses,
392           libraryClasses,
393           ImmutableSet.copyOf(mainDexList),
394           dexItemFactory,
395           highestSortingString,
396           timing);
397     }
398   }
399 }
400