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