DexMerger.java revision a6264bd14e66bd05f98d0b1b98ee9e044b93faf9
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.dx.merge; 18 19import com.android.dx.dex.SizeOf; 20import com.android.dx.dex.TableOfContents; 21import com.android.dx.io.Annotation; 22import com.android.dx.io.ClassData; 23import com.android.dx.io.ClassDef; 24import com.android.dx.io.Code; 25import com.android.dx.io.DexBuffer; 26import com.android.dx.io.DexHasher; 27import com.android.dx.io.FieldId; 28import com.android.dx.io.MethodId; 29import com.android.dx.io.ProtoId; 30import com.android.dx.util.DexException; 31import java.io.File; 32import java.io.IOException; 33import java.util.ArrayList; 34import java.util.Arrays; 35import java.util.Collections; 36import java.util.List; 37 38/** 39 * Combine two dex files into one. 40 */ 41public final class DexMerger { 42 private final DexBuffer dexA; 43 private final DexBuffer dexB; 44 private final CollisionPolicy collisionPolicy; 45 private final WriterSizes writerSizes; 46 47 private final DexBuffer dexOut = new DexBuffer(); 48 49 private final DexBuffer.Section headerOut; 50 51 /** All IDs and definitions sections */ 52 private final DexBuffer.Section idsDefsOut; 53 54 private final DexBuffer.Section mapListOut; 55 56 private final DexBuffer.Section typeListOut; 57 58 private final DexBuffer.Section classDataOut; 59 60 private final DexBuffer.Section codeOut; 61 62 private final DexBuffer.Section stringDataOut; 63 64 private final DexBuffer.Section debugInfoOut; 65 66 private final DexBuffer.Section encodedArrayOut; 67 68 /** annotations directory on a type */ 69 private final DexBuffer.Section annotationsDirectoryOut; 70 71 /** sets of annotations on a member, parameter or type */ 72 private final DexBuffer.Section annotationSetOut; 73 74 /** parameter lists */ 75 private final DexBuffer.Section annotationSetRefListOut; 76 77 /** individual annotations, each containing zero or more fields */ 78 private final DexBuffer.Section annotationOut; 79 80 private final TableOfContents contentsOut; 81 82 private final IndexMap aIndexMap; 83 private final IndexMap bIndexMap; 84 private final InstructionTransformer aInstructionTransformer; 85 private final InstructionTransformer bInstructionTransformer; 86 87 /** minimum number of wasted bytes before it's worthwhile to compact the result */ 88 private int compactWasteThreshold = 1024 * 1024; // 1MiB 89 90 public DexMerger(DexBuffer dexA, DexBuffer dexB, CollisionPolicy collisionPolicy) 91 throws IOException { 92 this(dexA, dexB, collisionPolicy, new WriterSizes(dexA, dexB)); 93 } 94 95 private DexMerger(DexBuffer dexA, DexBuffer dexB, CollisionPolicy collisionPolicy, 96 WriterSizes writerSizes) throws IOException { 97 this.dexA = dexA; 98 this.dexB = dexB; 99 this.collisionPolicy = collisionPolicy; 100 this.writerSizes = writerSizes; 101 102 TableOfContents aContents = dexA.getTableOfContents(); 103 TableOfContents bContents = dexB.getTableOfContents(); 104 aIndexMap = new IndexMap(dexOut, aContents); 105 bIndexMap = new IndexMap(dexOut, bContents); 106 aInstructionTransformer = new InstructionTransformer(aIndexMap); 107 bInstructionTransformer = new InstructionTransformer(bIndexMap); 108 109 headerOut = dexOut.appendSection(writerSizes.header, "header"); 110 idsDefsOut = dexOut.appendSection(writerSizes.idsDefs, "ids defs"); 111 112 contentsOut = dexOut.getTableOfContents(); 113 contentsOut.dataOff = dexOut.getLength(); 114 115 contentsOut.mapList.off = dexOut.getLength(); 116 contentsOut.mapList.size = 1; 117 mapListOut = dexOut.appendSection(writerSizes.mapList, "map list"); 118 119 contentsOut.typeLists.off = dexOut.getLength(); 120 typeListOut = dexOut.appendSection(writerSizes.typeList, "type list"); 121 122 contentsOut.annotationSetRefLists.off = dexOut.getLength(); 123 annotationSetRefListOut = dexOut.appendSection( 124 writerSizes.annotationsSetRefList, "annotation set ref list"); 125 126 contentsOut.annotationSets.off = dexOut.getLength(); 127 annotationSetOut = dexOut.appendSection(writerSizes.annotationsSet, "annotation sets"); 128 129 contentsOut.classDatas.off = dexOut.getLength(); 130 classDataOut = dexOut.appendSection(writerSizes.classData, "class data"); 131 132 contentsOut.codes.off = dexOut.getLength(); 133 codeOut = dexOut.appendSection(writerSizes.code, "code"); 134 135 contentsOut.stringDatas.off = dexOut.getLength(); 136 stringDataOut = dexOut.appendSection(writerSizes.stringData, "string data"); 137 138 contentsOut.debugInfos.off = dexOut.getLength(); 139 debugInfoOut = dexOut.appendSection(writerSizes.debugInfo, "debug info"); 140 141 contentsOut.annotations.off = dexOut.getLength(); 142 annotationOut = dexOut.appendSection(writerSizes.annotation, "annotation"); 143 144 contentsOut.encodedArrays.off = dexOut.getLength(); 145 encodedArrayOut = dexOut.appendSection(writerSizes.encodedArray, "encoded array"); 146 147 contentsOut.annotationsDirectories.off = dexOut.getLength(); 148 annotationsDirectoryOut = dexOut.appendSection( 149 writerSizes.annotationsDirectory, "annotations directory"); 150 151 dexOut.noMoreSections(); 152 contentsOut.dataSize = dexOut.getLength() - contentsOut.dataOff; 153 } 154 155 public void setCompactWasteThreshold(int compactWasteThreshold) { 156 this.compactWasteThreshold = compactWasteThreshold; 157 } 158 159 private DexBuffer mergeDexBuffers() throws IOException { 160 mergeStringIds(); 161 mergeTypeIds(); 162 mergeTypeLists(); 163 mergeProtoIds(); 164 mergeFieldIds(); 165 mergeMethodIds(); 166 mergeAnnotations(); 167 unionAnnotationSetsAndDirectories(); 168 mergeClassDefs(); 169 170 // write the header 171 contentsOut.header.off = 0; 172 contentsOut.header.size = 1; 173 contentsOut.fileSize = dexOut.getLength(); 174 contentsOut.computeSizesFromOffsets(); 175 contentsOut.writeHeader(headerOut); 176 contentsOut.writeMap(mapListOut); 177 178 // generate and write the hashes 179 new DexHasher().writeHashes(dexOut); 180 181 return dexOut; 182 } 183 184 public DexBuffer merge() throws IOException { 185 long start = System.nanoTime(); 186 DexBuffer result = mergeDexBuffers(); 187 188 /* 189 * We use pessimistic sizes when merging dex files. If those sizes 190 * result in too many bytes wasted, compact the result. To compact, 191 * simply merge the result with itself. 192 */ 193 WriterSizes compactedSizes = writerSizes.clone(); 194 compactedSizes.minusWaste(this); 195 int wastedByteCount = writerSizes.size() - compactedSizes.size(); 196 if (wastedByteCount > + compactWasteThreshold) { 197 DexMerger compacter = new DexMerger( 198 dexOut, new DexBuffer(), CollisionPolicy.FAIL, compactedSizes); 199 result = compacter.mergeDexBuffers(); 200 System.out.printf("Result compacted from %.1fKiB to %.1fKiB to save %.1fKiB%n", 201 dexOut.getLength() / 1024f, 202 result.getLength() / 1024f, 203 wastedByteCount / 1024f); 204 } 205 206 long elapsed = System.nanoTime() - start; 207 System.out.printf("Merged dex A (%d defs/%.1fKiB) with dex B " 208 + "(%d defs/%.1fKiB). Result is %d defs/%.1fKiB. Took %.1fs%n", 209 dexA.getTableOfContents().classDefs.size, 210 dexA.getLength() / 1024f, 211 dexB.getTableOfContents().classDefs.size, 212 dexB.getLength() / 1024f, 213 result.getTableOfContents().classDefs.size, 214 result.getLength() / 1024f, 215 elapsed / 1000000000f); 216 217 return result; 218 } 219 220 /** 221 * Reads an IDs section of two dex files and writes an IDs section of a 222 * merged dex file. Populates maps from old to new indices in the process. 223 */ 224 abstract class IdMerger<T extends Comparable<T>> { 225 private final DexBuffer.Section out; 226 227 protected IdMerger(DexBuffer.Section out) { 228 this.out = out; 229 } 230 231 /** 232 * Merges already-sorted sections, reading only two values into memory 233 * at a time. 234 */ 235 public final void mergeSorted() { 236 TableOfContents.Section aSection = getSection(dexA.getTableOfContents()); 237 TableOfContents.Section bSection = getSection(dexB.getTableOfContents()); 238 getSection(contentsOut).off = out.getPosition(); 239 240 DexBuffer.Section inA = aSection.exists() ? dexA.open(aSection.off) : null; 241 DexBuffer.Section inB = bSection.exists() ? dexB.open(bSection.off) : null; 242 int aOffset = -1; 243 int bOffset = -1; 244 int aIndex = 0; 245 int bIndex = 0; 246 int outCount = 0; 247 T a = null; 248 T b = null; 249 250 while (true) { 251 if (a == null && aIndex < aSection.size) { 252 aOffset = inA.getPosition(); 253 a = read(inA, aIndexMap, aIndex); 254 } 255 if (b == null && bIndex < bSection.size) { 256 bOffset = inB.getPosition(); 257 b = read(inB, bIndexMap, bIndex); 258 } 259 260 // Write the smaller of a and b. If they're equal, write only once 261 boolean advanceA; 262 boolean advanceB; 263 if (a != null && b != null) { 264 int compare = a.compareTo(b); 265 advanceA = compare <= 0; 266 advanceB = compare >= 0; 267 } else { 268 advanceA = (a != null); 269 advanceB = (b != null); 270 } 271 272 T toWrite = null; 273 if (advanceA) { 274 toWrite = a; 275 updateIndex(aOffset, aIndexMap, aIndex++, outCount); 276 a = null; 277 aOffset = -1; 278 } 279 if (advanceB) { 280 toWrite = b; 281 updateIndex(bOffset, bIndexMap, bIndex++, outCount); 282 b = null; 283 bOffset = -1; 284 } 285 if (toWrite == null) { 286 break; // advanceA == false && advanceB == false 287 } 288 write(toWrite); 289 outCount++; 290 } 291 292 getSection(contentsOut).size = outCount; 293 } 294 295 /** 296 * Merges unsorted sections by reading them completely into memory and 297 * sorting in memory. 298 */ 299 public final void mergeUnsorted() { 300 getSection(contentsOut).off = out.getPosition(); 301 302 List<UnsortedValue> all = new ArrayList<UnsortedValue>(); 303 all.addAll(readUnsortedValues(dexA, aIndexMap)); 304 all.addAll(readUnsortedValues(dexB, bIndexMap)); 305 Collections.sort(all); 306 307 int outCount = 0; 308 for (int i = 0; i < all.size(); ) { 309 UnsortedValue e1 = all.get(i++); 310 updateIndex(e1.offset, getIndexMap(e1.source), e1.index, outCount - 1); 311 312 while (i < all.size() && e1.compareTo(all.get(i)) == 0) { 313 UnsortedValue e2 = all.get(i++); 314 updateIndex(e2.offset, getIndexMap(e2.source), e2.index, outCount - 1); 315 } 316 317 write(e1.value); 318 outCount++; 319 } 320 321 getSection(contentsOut).size = outCount; 322 } 323 324 private List<UnsortedValue> readUnsortedValues(DexBuffer source, IndexMap indexMap) { 325 TableOfContents.Section section = getSection(source.getTableOfContents()); 326 if (!section.exists()) { 327 return Collections.emptyList(); 328 } 329 330 List<UnsortedValue> result = new ArrayList<UnsortedValue>(); 331 DexBuffer.Section in = source.open(section.off); 332 for (int i = 0; i < section.size; i++) { 333 int offset = in.getPosition(); 334 T value = read(in, indexMap, 0); 335 result.add(new UnsortedValue(source, indexMap, value, i, offset)); 336 } 337 return result; 338 } 339 340 abstract TableOfContents.Section getSection(TableOfContents tableOfContents); 341 abstract T read(DexBuffer.Section in, IndexMap indexMap, int index); 342 abstract void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex); 343 abstract void write(T value); 344 345 class UnsortedValue implements Comparable<UnsortedValue> { 346 final DexBuffer source; 347 final IndexMap indexMap; 348 final T value; 349 final int index; 350 final int offset; 351 352 UnsortedValue(DexBuffer source, IndexMap indexMap, T value, int index, int offset) { 353 this.source = source; 354 this.indexMap = indexMap; 355 this.value = value; 356 this.index = index; 357 this.offset = offset; 358 } 359 360 public int compareTo(UnsortedValue unsortedValue) { 361 return value.compareTo(unsortedValue.value); 362 } 363 } 364 } 365 366 private IndexMap getIndexMap(DexBuffer dexBuffer) { 367 if (dexBuffer == dexA) { 368 return aIndexMap; 369 } else if (dexBuffer == dexB) { 370 return bIndexMap; 371 } else { 372 throw new IllegalArgumentException(); 373 } 374 } 375 376 private void mergeStringIds() { 377 new IdMerger<String>(idsDefsOut) { 378 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 379 return tableOfContents.stringIds; 380 } 381 382 @Override String read(DexBuffer.Section in, IndexMap indexMap, int index) { 383 return in.readString(); 384 } 385 386 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 387 indexMap.stringIds[oldIndex] = newIndex; 388 } 389 390 @Override void write(String value) { 391 contentsOut.stringDatas.size++; 392 idsDefsOut.writeInt(stringDataOut.getPosition()); 393 stringDataOut.writeStringData(value); 394 } 395 }.mergeSorted(); 396 } 397 398 private void mergeTypeIds() { 399 new IdMerger<Integer>(idsDefsOut) { 400 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 401 return tableOfContents.typeIds; 402 } 403 404 @Override Integer read(DexBuffer.Section in, IndexMap indexMap, int index) { 405 int stringIndex = in.readInt(); 406 return indexMap.adjustString(stringIndex); 407 } 408 409 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 410 indexMap.typeIds[oldIndex] = (short) newIndex; 411 } 412 413 @Override void write(Integer value) { 414 idsDefsOut.writeInt(value); 415 } 416 }.mergeSorted(); 417 } 418 419 private void mergeTypeLists() { 420 new IdMerger<TypeList>(typeListOut) { 421 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 422 return tableOfContents.typeLists; 423 } 424 425 @Override TypeList read(DexBuffer.Section in, IndexMap indexMap, int index) { 426 return indexMap.adjustTypeList(in.readTypeList()); 427 } 428 429 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 430 indexMap.putTypeListOffset(offset, typeListOut.getPosition()); 431 } 432 433 @Override void write(TypeList value) { 434 typeListOut.writeTypeList(value); 435 } 436 }.mergeUnsorted(); 437 } 438 439 private void mergeProtoIds() { 440 new IdMerger<ProtoId>(idsDefsOut) { 441 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 442 return tableOfContents.protoIds; 443 } 444 445 @Override ProtoId read(DexBuffer.Section in, IndexMap indexMap, int index) { 446 return indexMap.adjust(in.readProtoId()); 447 } 448 449 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 450 indexMap.protoIds[oldIndex] = (short) newIndex; 451 } 452 453 @Override void write(ProtoId value) { 454 value.writeTo(idsDefsOut); 455 } 456 }.mergeSorted(); 457 } 458 459 private void mergeFieldIds() { 460 new IdMerger<FieldId>(idsDefsOut) { 461 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 462 return tableOfContents.fieldIds; 463 } 464 465 @Override FieldId read(DexBuffer.Section in, IndexMap indexMap, int index) { 466 return indexMap.adjust(in.readFieldId()); 467 } 468 469 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 470 indexMap.fieldIds[oldIndex] = (short) newIndex; 471 } 472 473 @Override void write(FieldId value) { 474 value.writeTo(idsDefsOut); 475 } 476 }.mergeSorted(); 477 } 478 479 private void mergeMethodIds() { 480 new IdMerger<MethodId>(idsDefsOut) { 481 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 482 return tableOfContents.methodIds; 483 } 484 485 @Override MethodId read(DexBuffer.Section in, IndexMap indexMap, int index) { 486 return indexMap.adjust(in.readMethodId()); 487 } 488 489 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 490 indexMap.methodIds[oldIndex] = (short) newIndex; 491 } 492 493 @Override void write(MethodId methodId) { 494 methodId.writeTo(idsDefsOut); 495 } 496 }.mergeSorted(); 497 } 498 499 private void mergeAnnotations() { 500 new IdMerger<Annotation>(annotationOut) { 501 @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { 502 return tableOfContents.annotations; 503 } 504 505 @Override Annotation read(DexBuffer.Section in, IndexMap indexMap, int index) { 506 return indexMap.adjust(in.readAnnotation()); 507 } 508 509 @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { 510 indexMap.putAnnotationOffset(offset, annotationOut.getPosition()); 511 } 512 513 @Override void write(Annotation value) { 514 value.writeTo(annotationOut); 515 } 516 }.mergeUnsorted(); 517 } 518 519 private void mergeClassDefs() { 520 SortableType[] types = getSortedTypes(); 521 contentsOut.classDefs.off = idsDefsOut.getPosition(); 522 contentsOut.classDefs.size = types.length; 523 524 for (SortableType type : types) { 525 DexBuffer in = type.getBuffer(); 526 IndexMap indexMap = (in == dexA) ? aIndexMap : bIndexMap; 527 transformClassDef(in, type.getClassDef(), indexMap); 528 } 529 } 530 531 /** 532 * Returns the union of classes from both files, sorted in order such that 533 * a class is always preceded by its supertype and implemented interfaces. 534 */ 535 private SortableType[] getSortedTypes() { 536 // size is pessimistic; doesn't include arrays 537 SortableType[] sortableTypes = new SortableType[contentsOut.typeIds.size]; 538 readSortableTypes(sortableTypes, dexA, aIndexMap); 539 readSortableTypes(sortableTypes, dexB, bIndexMap); 540 541 /* 542 * Populate the depths of each sortable type. This makes D iterations 543 * through all N types, where 'D' is the depth of the deepest type. For 544 * example, the deepest class in libcore is Xalan's KeyIterator, which 545 * is 11 types deep. 546 */ 547 while (true) { 548 boolean allDone = true; 549 for (SortableType sortableType : sortableTypes) { 550 if (sortableType != null && !sortableType.isDepthAssigned()) { 551 allDone &= sortableType.tryAssignDepth(sortableTypes); 552 } 553 } 554 if (allDone) { 555 break; 556 } 557 } 558 559 // Now that all types have depth information, the result can be sorted 560 Arrays.sort(sortableTypes, SortableType.NULLS_LAST_ORDER); 561 562 // Strip nulls from the end 563 int firstNull = Arrays.asList(sortableTypes).indexOf(null); 564 return firstNull != -1 565 ? Arrays.copyOfRange(sortableTypes, 0, firstNull) 566 : sortableTypes; 567 } 568 569 /** 570 * Reads just enough data on each class so that we can sort it and then find 571 * it later. 572 */ 573 private void readSortableTypes(SortableType[] sortableTypes, DexBuffer buffer, 574 IndexMap indexMap) { 575 for (ClassDef classDef : buffer.classDefs()) { 576 SortableType sortableType = indexMap.adjust(new SortableType(buffer, classDef)); 577 int t = sortableType.getTypeIndex(); 578 if (sortableTypes[t] == null) { 579 sortableTypes[t] = sortableType; 580 } else if (collisionPolicy != CollisionPolicy.KEEP_FIRST) { 581 throw new DexException("Multiple dex files define " 582 + buffer.typeNames().get(classDef.getTypeIndex())); 583 } 584 } 585 } 586 587 /** 588 * Copy annotation sets from each input to the output. 589 * 590 * TODO: this may write multiple copies of the same annotation set. 591 * We should shrink the output by merging rather than unioning 592 */ 593 private void unionAnnotationSetsAndDirectories() { 594 transformAnnotationSets(dexA, aIndexMap); 595 transformAnnotationSets(dexB, bIndexMap); 596 transformAnnotationDirectories(dexA, aIndexMap); 597 transformAnnotationDirectories(dexB, bIndexMap); 598 } 599 600 private void transformAnnotationSets(DexBuffer in, IndexMap indexMap) { 601 TableOfContents.Section section = in.getTableOfContents().annotationSets; 602 if (section.exists()) { 603 DexBuffer.Section setIn = in.open(section.off); 604 for (int i = 0; i < section.size; i++) { 605 transformAnnotationSet(indexMap, setIn); 606 } 607 } 608 } 609 610 private void transformAnnotationDirectories(DexBuffer in, IndexMap indexMap) { 611 TableOfContents.Section section = in.getTableOfContents().annotationsDirectories; 612 if (section.exists()) { 613 DexBuffer.Section directoryIn = in.open(section.off); 614 for (int i = 0; i < section.size; i++) { 615 transformAnnotationDirectory(in, directoryIn, indexMap); 616 } 617 } 618 } 619 620 /** 621 * Reads a class_def_item beginning at {@code in} and writes the index and 622 * data. 623 */ 624 private void transformClassDef(DexBuffer in, ClassDef classDef, IndexMap indexMap) { 625 idsDefsOut.assertFourByteAligned(); 626 idsDefsOut.writeInt(classDef.getTypeIndex()); 627 idsDefsOut.writeInt(classDef.getAccessFlags()); 628 idsDefsOut.writeInt(classDef.getSupertypeIndex()); 629 idsDefsOut.writeInt(classDef.getInterfacesOffset()); 630 631 int sourceFileIndex = indexMap.adjustString(classDef.getSourceFileIndex()); 632 idsDefsOut.writeInt(sourceFileIndex); 633 634 int annotationsOff = classDef.getAnnotationsOffset(); 635 idsDefsOut.writeInt(indexMap.adjustAnnotationDirectory(annotationsOff)); 636 637 int classDataOff = classDef.getClassDataOffset(); 638 if (classDataOff == 0) { 639 idsDefsOut.writeInt(0); 640 } else { 641 idsDefsOut.writeInt(classDataOut.getPosition()); 642 ClassData classData = in.readClassData(classDef); 643 transformClassData(in, classData, indexMap); 644 } 645 646 int staticValuesOff = classDef.getStaticValuesOffset(); 647 if (staticValuesOff == 0) { 648 idsDefsOut.writeInt(0); 649 } else { 650 DexBuffer.Section staticValuesIn = in.open(staticValuesOff); 651 idsDefsOut.writeInt(encodedArrayOut.getPosition()); 652 transformStaticValues(staticValuesIn, indexMap); 653 } 654 } 655 656 /** 657 * Transform all annotations on a class. 658 */ 659 private void transformAnnotationDirectory( 660 DexBuffer in, DexBuffer.Section directoryIn, IndexMap indexMap) { 661 contentsOut.annotationsDirectories.size++; 662 annotationsDirectoryOut.assertFourByteAligned(); 663 indexMap.putAnnotationDirectoryOffset( 664 directoryIn.getPosition(), annotationsDirectoryOut.getPosition()); 665 666 int classAnnotationsOffset = indexMap.adjustAnnotationSet(directoryIn.readInt()); 667 annotationsDirectoryOut.writeInt(classAnnotationsOffset); 668 669 int fieldsSize = directoryIn.readInt(); 670 annotationsDirectoryOut.writeInt(fieldsSize); 671 672 int methodsSize = directoryIn.readInt(); 673 annotationsDirectoryOut.writeInt(methodsSize); 674 675 int parameterListSize = directoryIn.readInt(); 676 annotationsDirectoryOut.writeInt(parameterListSize); 677 678 for (int i = 0; i < fieldsSize; i++) { 679 // field index 680 annotationsDirectoryOut.writeInt(indexMap.adjustField(directoryIn.readInt())); 681 682 // annotations offset 683 annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt())); 684 } 685 686 for (int i = 0; i < methodsSize; i++) { 687 // method index 688 annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt())); 689 690 // annotation set offset 691 annotationsDirectoryOut.writeInt( 692 indexMap.adjustAnnotationSet(directoryIn.readInt())); 693 } 694 695 for (int i = 0; i < parameterListSize; i++) { 696 contentsOut.annotationSetRefLists.size++; 697 annotationSetRefListOut.assertFourByteAligned(); 698 699 // method index 700 annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt())); 701 702 // annotations offset 703 annotationsDirectoryOut.writeInt(annotationSetRefListOut.getPosition()); 704 DexBuffer.Section refListIn = in.open(directoryIn.readInt()); 705 706 // parameters 707 int parameterCount = refListIn.readInt(); 708 annotationSetRefListOut.writeInt(parameterCount); 709 for (int p = 0; p < parameterCount; p++) { 710 annotationSetRefListOut.writeInt(indexMap.adjustAnnotationSet(refListIn.readInt())); 711 } 712 } 713 } 714 715 /** 716 * Transform all annotations on a single type, member or parameter. 717 */ 718 private void transformAnnotationSet(IndexMap indexMap, DexBuffer.Section setIn) { 719 contentsOut.annotationSets.size++; 720 annotationSetOut.assertFourByteAligned(); 721 indexMap.putAnnotationSetOffset(setIn.getPosition(), annotationSetOut.getPosition()); 722 723 int size = setIn.readInt(); 724 annotationSetOut.writeInt(size); 725 726 for (int j = 0; j < size; j++) { 727 annotationSetOut.writeInt(indexMap.adjustAnnotation(setIn.readInt())); 728 } 729 } 730 731 private void transformClassData(DexBuffer in, ClassData classData, IndexMap indexMap) { 732 contentsOut.classDatas.size++; 733 734 ClassData.Field[] staticFields = classData.getStaticFields(); 735 ClassData.Field[] instanceFields = classData.getInstanceFields(); 736 ClassData.Method[] directMethods = classData.getDirectMethods(); 737 ClassData.Method[] virtualMethods = classData.getVirtualMethods(); 738 739 classDataOut.writeUleb128(staticFields.length); 740 classDataOut.writeUleb128(instanceFields.length); 741 classDataOut.writeUleb128(directMethods.length); 742 classDataOut.writeUleb128(virtualMethods.length); 743 744 transformFields(indexMap, staticFields); 745 transformFields(indexMap, instanceFields); 746 transformMethods(in, indexMap, directMethods); 747 transformMethods(in, indexMap, virtualMethods); 748 } 749 750 private void transformFields(IndexMap indexMap, ClassData.Field[] fields) { 751 int lastOutFieldIndex = 0; 752 for (ClassData.Field field : fields) { 753 int outFieldIndex = indexMap.adjustField(field.getFieldIndex()); 754 classDataOut.writeUleb128(outFieldIndex - lastOutFieldIndex); 755 lastOutFieldIndex = outFieldIndex; 756 classDataOut.writeUleb128(field.getAccessFlags()); 757 } 758 } 759 760 private void transformMethods(DexBuffer in, IndexMap indexMap, ClassData.Method[] methods) { 761 int lastOutMethodIndex = 0; 762 for (ClassData.Method method : methods) { 763 int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex()); 764 classDataOut.writeUleb128(outMethodIndex - lastOutMethodIndex); 765 lastOutMethodIndex = outMethodIndex; 766 767 classDataOut.writeUleb128(method.getAccessFlags()); 768 769 if (method.getCodeOffset() == 0) { 770 classDataOut.writeUleb128(0); 771 } else { 772 codeOut.alignToFourBytes(); 773 classDataOut.writeUleb128(codeOut.getPosition()); 774 transformCode(in, in.readCode(method), indexMap); 775 } 776 } 777 } 778 779 private void transformCode(DexBuffer in, Code code, IndexMap indexMap) { 780 contentsOut.codes.size++; 781 codeOut.assertFourByteAligned(); 782 783 codeOut.writeUnsignedShort(code.getRegistersSize()); 784 codeOut.writeUnsignedShort(code.getInsSize()); 785 codeOut.writeUnsignedShort(code.getOutsSize()); 786 787 Code.Try[] tries = code.getTries(); 788 codeOut.writeUnsignedShort(tries.length); 789 790 // TODO: retain debug info 791 // code.getDebugInfoOffset(); 792 codeOut.writeInt(0); 793 794 short[] instructions = code.getInstructions(); 795 InstructionTransformer transformer = (in == dexA) 796 ? aInstructionTransformer 797 : bInstructionTransformer; 798 short[] newInstructions = transformer.transform(instructions); 799 codeOut.writeInt(newInstructions.length); 800 codeOut.write(newInstructions); 801 802 if (tries.length > 0) { 803 if (newInstructions.length % 2 == 1) { 804 codeOut.writeShort((short) 0); // padding 805 } 806 for (Code.Try tryItem : tries) { 807 codeOut.writeInt(tryItem.getStartAddress()); 808 codeOut.writeUnsignedShort(tryItem.getInstructionCount()); 809 codeOut.writeUnsignedShort(tryItem.getHandlerOffset()); 810 } 811 Code.CatchHandler[] catchHandlers = code.getCatchHandlers(); 812 codeOut.writeUleb128(catchHandlers.length); 813 for (Code.CatchHandler catchHandler : catchHandlers) { 814 transformEncodedCatchHandler(catchHandler, indexMap); 815 } 816 } 817 } 818 819 private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) { 820 int catchAllAddress = catchHandler.getCatchAllAddress(); 821 int[] typeIndexes = catchHandler.getTypeIndexes(); 822 int[] addresses = catchHandler.getAddresses(); 823 824 if (catchAllAddress != -1) { 825 codeOut.writeSleb128(-typeIndexes.length); 826 } else { 827 codeOut.writeSleb128(typeIndexes.length); 828 } 829 830 for (int i = 0; i < typeIndexes.length; i++) { 831 codeOut.writeUleb128(indexMap.adjustType(typeIndexes[i])); 832 codeOut.writeUleb128(addresses[i]); 833 } 834 835 if (catchAllAddress != -1) { 836 codeOut.writeUleb128(catchAllAddress); 837 } 838 } 839 840 private void transformStaticValues(DexBuffer.Section in, IndexMap indexMap) { 841 contentsOut.encodedArrays.size++; 842 indexMap.adjustEncodedArray(in.readEncodedArray()).writeTo(encodedArrayOut); 843 } 844 845 /** 846 * Byte counts for the sections written when creating a dex. Target sizes 847 * are defined in one of two ways: 848 * <ul> 849 * <li>By pessimistically guessing how large the union of dex files will be. 850 * We're pessimistic because we can't predict the amount of duplication 851 * between dex files, nor can we predict the length of ULEB-encoded 852 * offsets or indices. 853 * <li>By exactly measuring an existing dex. 854 * </ul> 855 */ 856 private static class WriterSizes implements Cloneable { 857 private int header = SizeOf.HEADER_ITEM; 858 private int idsDefs; 859 private int mapList; 860 private int typeList; 861 private int classData; 862 private int code; 863 private int stringData; 864 private int debugInfo; 865 private int encodedArray; 866 private int annotationsDirectory; 867 private int annotationsSet; 868 private int annotationsSetRefList; 869 private int annotation; 870 871 /** 872 * Compute sizes for merging a and b. 873 */ 874 public WriterSizes(DexBuffer a, DexBuffer b) { 875 plus(a.getTableOfContents(), false); 876 plus(b.getTableOfContents(), false); 877 } 878 879 @Override public WriterSizes clone() { 880 try { 881 return (WriterSizes) super.clone(); 882 } catch (CloneNotSupportedException e) { 883 throw new AssertionError(); 884 } 885 } 886 887 public void plus(TableOfContents contents, boolean exact) { 888 idsDefs += contents.stringIds.size * SizeOf.STRING_ID_ITEM 889 + contents.typeIds.size * SizeOf.TYPE_ID_ITEM 890 + contents.protoIds.size * SizeOf.PROTO_ID_ITEM 891 + contents.fieldIds.size * SizeOf.MEMBER_ID_ITEM 892 + contents.methodIds.size * SizeOf.MEMBER_ID_ITEM 893 + contents.classDefs.size * SizeOf.CLASS_DEF_ITEM; 894 mapList = SizeOf.UINT + (contents.sections.length * SizeOf.MAP_ITEM); 895 typeList += contents.typeLists.byteCount; 896 stringData += contents.stringDatas.byteCount; 897 debugInfo += contents.debugInfos.byteCount; 898 annotationsDirectory += contents.annotationsDirectories.byteCount; 899 annotationsSet += contents.annotationSets.byteCount; 900 annotationsSetRefList += contents.annotationSetRefLists.byteCount; 901 902 if (exact) { 903 code += contents.codes.byteCount; 904 classData += contents.classDatas.byteCount; 905 encodedArray += contents.encodedArrays.byteCount; 906 annotation += contents.annotations.byteCount; 907 } else { 908 // at most 1/4 of the bytes in a code section are uleb/sleb 909 code += (int) Math.ceil(contents.codes.byteCount * 1.25); 910 // at most 1/3 of the bytes in a class data section are uleb/sleb 911 classData += (int) Math.ceil(contents.classDatas.byteCount * 1.34); 912 // all of the bytes in an encoding arrays section may be uleb/sleb 913 encodedArray += contents.encodedArrays.byteCount * 2; 914 // at most 1/3 of the bytes in an encoding arrays section are uleb/sleb 915 annotation += (int) Math.ceil(contents.annotations.byteCount * 1.34); 916 } 917 } 918 919 public void minusWaste(DexMerger dexMerger) { 920 header -= dexMerger.headerOut.remaining(); 921 idsDefs -= dexMerger.idsDefsOut.remaining(); 922 mapList -= dexMerger.mapListOut.remaining(); 923 typeList -= dexMerger.typeListOut.remaining(); 924 classData -= dexMerger.classDataOut.remaining(); 925 code -= dexMerger.codeOut.remaining(); 926 stringData -= dexMerger.stringDataOut.remaining(); 927 debugInfo -= dexMerger.debugInfoOut.remaining(); 928 encodedArray -= dexMerger.encodedArrayOut.remaining(); 929 annotationsDirectory -= dexMerger.annotationsDirectoryOut.remaining(); 930 annotationsSet -= dexMerger.annotationSetOut.remaining(); 931 annotationsSetRefList -= dexMerger.annotationSetRefListOut.remaining(); 932 annotation -= dexMerger.annotationOut.remaining(); 933 } 934 935 public int size() { 936 return header + idsDefs + mapList + typeList + classData + code + stringData + debugInfo 937 + encodedArray + annotationsDirectory + annotationsSet + annotationsSetRefList 938 + annotation; 939 } 940 } 941 942 public static void main(String[] args) throws IOException { 943 if (args.length != 3) { 944 printUsage(); 945 return; 946 } 947 948 DexBuffer dexA = new DexBuffer(new File(args[1])); 949 DexBuffer dexB = new DexBuffer(new File(args[2])); 950 DexBuffer merged = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge(); 951 merged.writeTo(new File(args[0])); 952 } 953 954 private static void printUsage() { 955 System.out.println("Usage: DexMerger <out.dex> <a.dex> <b.dex>"); 956 System.out.println(); 957 System.out.println("If both a and b define the same classes, a's copy will be used."); 958 } 959} 960