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