1 /* 2 * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.java.util.jar.pack; 27 28 import java.util.jar.Pack200; 29 import com.sun.java.util.jar.pack.Attribute.Layout; 30 import com.sun.java.util.jar.pack.ConstantPool.ClassEntry; 31 import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry; 32 import com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry; 33 import com.sun.java.util.jar.pack.ConstantPool.Index; 34 import com.sun.java.util.jar.pack.ConstantPool.LiteralEntry; 35 import com.sun.java.util.jar.pack.ConstantPool.Utf8Entry; 36 import com.sun.java.util.jar.pack.ConstantPool.Entry; 37 import java.io.ByteArrayInputStream; 38 import java.io.ByteArrayOutputStream; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.io.OutputStream; 42 import java.io.SequenceInputStream; 43 import java.lang.reflect.Modifier; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collection; 47 import java.util.Collections; 48 import java.util.Comparator; 49 import java.util.HashMap; 50 import java.util.HashSet; 51 import java.util.Iterator; 52 import java.util.List; 53 import java.util.ListIterator; 54 import java.util.Map; 55 import java.util.Set; 56 import java.util.jar.JarFile; 57 import static com.sun.java.util.jar.pack.Constants.*; 58 59 /** 60 * Define the main data structure transmitted by pack/unpack. 61 * @author John Rose 62 */ 63 class Package { 64 int verbose; 65 { 66 PropMap pmap = Utils.currentPropMap(); 67 if (pmap != null) 68 verbose = pmap.getInteger(Utils.DEBUG_VERBOSE); 69 } 70 71 final int magic = JAVA_PACKAGE_MAGIC; 72 73 int default_modtime = NO_MODTIME; 74 int default_options = 0; // FO_DEFLATE_HINT 75 76 Version defaultClassVersion = null; 77 78 // These fields can be adjusted by driver properties. 79 final Version minClassVersion; 80 final Version maxClassVersion; 81 // null, indicates that consensus rules during package write 82 final Version packageVersion; 83 84 Version observedHighestClassVersion = null; 85 86 87 // What constants are used in this unit? 88 ConstantPool.IndexGroup cp = new ConstantPool.IndexGroup(); 89 90 /* 91 * typically used by the PackageReader to set the defaults, in which 92 * case we take the defaults. 93 */ 94 public Package() { 95 minClassVersion = JAVA_MIN_CLASS_VERSION; 96 maxClassVersion = JAVA_MAX_CLASS_VERSION; 97 packageVersion = null; 98 } 99 100 101 /* 102 * Typically used by the PackerImpl during before packing, the defaults are 103 * overridden by the users preferences. 104 */ 105 public Package(Version minClassVersion, Version maxClassVersion, Version packageVersion) { 106 // Fill in permitted range of major/minor version numbers. 107 this.minClassVersion = minClassVersion == null 108 ? JAVA_MIN_CLASS_VERSION 109 : minClassVersion; 110 this.maxClassVersion = maxClassVersion == null 111 ? JAVA_MAX_CLASS_VERSION 112 : maxClassVersion; 113 this.packageVersion = packageVersion; 114 } 115 116 117 public void reset() { 118 cp = new ConstantPool.IndexGroup(); 119 classes.clear(); 120 files.clear(); 121 BandStructure.nextSeqForDebug = 0; 122 observedHighestClassVersion = null; 123 } 124 125 // Special empty versions of Code and InnerClasses, used for markers. 126 public static final Attribute.Layout attrCodeEmpty; 127 public static final Attribute.Layout attrBootstrapMethodsEmpty; 128 public static final Attribute.Layout attrInnerClassesEmpty; 129 public static final Attribute.Layout attrSourceFileSpecial; 130 public static final Map<Attribute.Layout, Attribute> attrDefs; 131 static { 132 Map<Layout, Attribute> ad = new HashMap<>(3); 133 attrCodeEmpty = Attribute.define(ad, ATTR_CONTEXT_METHOD, 134 "Code", "").layout(); 135 attrBootstrapMethodsEmpty = Attribute.define(ad, ATTR_CONTEXT_CLASS, 136 "BootstrapMethods", "").layout(); 137 attrInnerClassesEmpty = Attribute.define(ad, ATTR_CONTEXT_CLASS, 138 "InnerClasses", "").layout(); 139 attrSourceFileSpecial = Attribute.define(ad, ATTR_CONTEXT_CLASS, 140 "SourceFile", "RUNH").layout(); 141 attrDefs = Collections.unmodifiableMap(ad); 142 } 143 144 Version getDefaultClassVersion() { 145 return defaultClassVersion; 146 } 147 148 /** Return the highest version number of all classes, 149 * or 0 if there are no classes. 150 */ 151 private void setHighestClassVersion() { 152 if (observedHighestClassVersion != null) 153 return; 154 Version res = JAVA_MIN_CLASS_VERSION; // initial low value 155 for (Class cls : classes) { 156 Version ver = cls.getVersion(); 157 if (res.lessThan(ver)) res = ver; 158 } 159 observedHighestClassVersion = res; 160 } 161 162 Version getHighestClassVersion() { 163 setHighestClassVersion(); 164 return observedHighestClassVersion; 165 } 166 167 // What Java classes are in this unit? 168 169 ArrayList<Package.Class> classes = new ArrayList<>(); 170 171 public List<Package.Class> getClasses() { 172 return classes; 173 } 174 175 public final 176 class Class extends Attribute.Holder implements Comparable<Class> { 177 public Package getPackage() { return Package.this; } 178 179 // Optional file characteristics and data source (a "class stub") 180 File file; 181 182 // File header 183 int magic; 184 Version version; 185 186 // Local constant pool (one-way mapping of index => package cp). 187 Entry[] cpMap; 188 189 // Class header 190 //int flags; // in Attribute.Holder.this.flags 191 ClassEntry thisClass; 192 ClassEntry superClass; 193 ClassEntry[] interfaces; 194 195 // Class parts 196 ArrayList<Field> fields; 197 ArrayList<Method> methods; 198 //ArrayList attributes; // in Attribute.Holder.this.attributes 199 // Note that InnerClasses may be collected at the package level. 200 ArrayList<InnerClass> innerClasses; 201 ArrayList<BootstrapMethodEntry> bootstrapMethods; 202 203 Class(int flags, ClassEntry thisClass, ClassEntry superClass, ClassEntry[] interfaces) { 204 this.magic = JAVA_MAGIC; 205 this.version = defaultClassVersion; 206 this.flags = flags; 207 this.thisClass = thisClass; 208 this.superClass = superClass; 209 this.interfaces = interfaces; 210 211 boolean added = classes.add(this); 212 assert(added); 213 } 214 215 Class(String classFile) { 216 // A blank class; must be read with a ClassReader, etc. 217 initFile(newStub(classFile)); 218 } 219 220 List<Field> getFields() { return fields == null ? noFields : fields; } 221 List<Method> getMethods() { return methods == null ? noMethods : methods; } 222 223 public String getName() { 224 return thisClass.stringValue(); 225 } 226 227 Version getVersion() { 228 return this.version; 229 } 230 231 // Note: equals and hashCode are identity-based. 232 public int compareTo(Class that) { 233 String n0 = this.getName(); 234 String n1 = that.getName(); 235 return n0.compareTo(n1); 236 } 237 238 String getObviousSourceFile() { 239 return Package.getObviousSourceFile(getName()); 240 } 241 242 private void transformSourceFile(boolean minimize) { 243 // Replace "obvious" SourceFile by null. 244 Attribute olda = getAttribute(attrSourceFileSpecial); 245 if (olda == null) 246 return; // no SourceFile attr. 247 String obvious = getObviousSourceFile(); 248 List<Entry> ref = new ArrayList<>(1); 249 olda.visitRefs(this, VRM_PACKAGE, ref); 250 Utf8Entry sfName = (Utf8Entry) ref.get(0); 251 Attribute a = olda; 252 if (sfName == null) { 253 if (minimize) { 254 // A pair of zero bytes. Cannot use predef. layout. 255 a = Attribute.find(ATTR_CONTEXT_CLASS, "SourceFile", "H"); 256 a = a.addContent(new byte[2]); 257 } else { 258 // Expand null attribute to the obvious string. 259 byte[] bytes = new byte[2]; 260 sfName = getRefString(obvious); 261 Object f = null; 262 f = Fixups.addRefWithBytes(f, bytes, sfName); 263 a = attrSourceFileSpecial.addContent(bytes, f); 264 } 265 } else if (obvious.equals(sfName.stringValue())) { 266 if (minimize) { 267 // Replace by an all-zero attribute. 268 a = attrSourceFileSpecial.addContent(new byte[2]); 269 } else { 270 assert(false); 271 } 272 } 273 if (a != olda) { 274 if (verbose > 2) 275 Utils.log.fine("recoding obvious SourceFile="+obvious); 276 List<Attribute> newAttrs = new ArrayList<>(getAttributes()); 277 int where = newAttrs.indexOf(olda); 278 newAttrs.set(where, a); 279 setAttributes(newAttrs); 280 } 281 } 282 283 void minimizeSourceFile() { 284 transformSourceFile(true); 285 } 286 void expandSourceFile() { 287 transformSourceFile(false); 288 } 289 290 protected Entry[] getCPMap() { 291 return cpMap; 292 } 293 294 protected void setCPMap(Entry[] cpMap) { 295 this.cpMap = cpMap; 296 } 297 298 boolean hasBootstrapMethods() { 299 return bootstrapMethods != null && !bootstrapMethods.isEmpty(); 300 } 301 302 List<BootstrapMethodEntry> getBootstrapMethods() { 303 return bootstrapMethods; 304 } 305 306 BootstrapMethodEntry[] getBootstrapMethodMap() { 307 return (hasBootstrapMethods()) 308 ? bootstrapMethods.toArray(new BootstrapMethodEntry[bootstrapMethods.size()]) 309 : null; 310 } 311 312 void setBootstrapMethods(Collection<BootstrapMethodEntry> bsms) { 313 assert(bootstrapMethods == null); // do not do this twice 314 bootstrapMethods = new ArrayList<>(bsms); 315 } 316 317 boolean hasInnerClasses() { 318 return innerClasses != null; 319 } 320 List<InnerClass> getInnerClasses() { 321 return innerClasses; 322 } 323 324 public void setInnerClasses(Collection<InnerClass> ics) { 325 innerClasses = (ics == null) ? null : new ArrayList<>(ics); 326 // Edit the attribute list, if necessary. 327 Attribute a = getAttribute(attrInnerClassesEmpty); 328 if (innerClasses != null && a == null) 329 addAttribute(attrInnerClassesEmpty.canonicalInstance()); 330 else if (innerClasses == null && a != null) 331 removeAttribute(a); 332 } 333 334 /** Given a global map of ICs (keyed by thisClass), 335 * compute the subset of its Map.values which are 336 * required to be present in the local InnerClasses 337 * attribute. Perform this calculation without 338 * reference to any actual InnerClasses attribute. 339 * <p> 340 * The order of the resulting list is consistent 341 * with that of Package.this.allInnerClasses. 342 */ 343 public List<InnerClass> computeGloballyImpliedICs() { 344 Set<Entry> cpRefs = new HashSet<>(); 345 { // This block temporarily displaces this.innerClasses. 346 ArrayList<InnerClass> innerClassesSaved = innerClasses; 347 innerClasses = null; // ignore for the moment 348 visitRefs(VRM_CLASSIC, cpRefs); 349 innerClasses = innerClassesSaved; 350 } 351 ConstantPool.completeReferencesIn(cpRefs, true); 352 353 Set<Entry> icRefs = new HashSet<>(); 354 for (Entry e : cpRefs) { 355 // Restrict cpRefs to InnerClasses entries only. 356 if (!(e instanceof ClassEntry)) continue; 357 // For every IC reference, add its outers also. 358 while (e != null) { 359 InnerClass ic = getGlobalInnerClass(e); 360 if (ic == null) break; 361 if (!icRefs.add(e)) break; 362 e = ic.outerClass; 363 // If we add A$B$C to the mix, we must also add A$B. 364 } 365 } 366 // This loop is structured this way so as to accumulate 367 // entries into impliedICs in an order which reflects 368 // the order of allInnerClasses. 369 ArrayList<InnerClass> impliedICs = new ArrayList<>(); 370 for (InnerClass ic : allInnerClasses) { 371 // This one is locally relevant if it describes 372 // a member of the current class, or if the current 373 // class uses it somehow. In the particular case 374 // where thisClass is an inner class, it will already 375 // be a member of icRefs. 376 if (icRefs.contains(ic.thisClass) 377 || ic.outerClass == this.thisClass) { 378 // Add every relevant class to the IC attribute: 379 if (verbose > 1) 380 Utils.log.fine("Relevant IC: "+ic); 381 impliedICs.add(ic); 382 } 383 } 384 return impliedICs; 385 } 386 387 // Helper for both minimizing and expanding. 388 // Computes a symmetric difference. 389 private List<InnerClass> computeICdiff() { 390 List<InnerClass> impliedICs = computeGloballyImpliedICs(); 391 List<InnerClass> actualICs = getInnerClasses(); 392 if (actualICs == null) 393 actualICs = Collections.emptyList(); 394 395 // Symmetric difference is calculated from I, A like this: 396 // diff = (I+A) - (I*A) 397 // Note that the center C is unordered, but the result 398 // preserves the original ordering of I and A. 399 // 400 // Class file rules require that outers precede inners. 401 // So, add I before A, in case A$B$Z is local, but A$B 402 // is implicit. The reverse is never the case. 403 if (actualICs.isEmpty()) { 404 return impliedICs; 405 // Diff is I since A is empty. 406 } 407 if (impliedICs.isEmpty()) { 408 return actualICs; 409 // Diff is A since I is empty. 410 } 411 // (I*A) is non-trivial 412 Set<InnerClass> center = new HashSet<>(actualICs); 413 center.retainAll(new HashSet<>(impliedICs)); 414 impliedICs.addAll(actualICs); 415 impliedICs.removeAll(center); 416 // Diff is now I^A = (I+A)-(I*A). 417 return impliedICs; 418 } 419 420 /** When packing, anticipate the effect of expandLocalICs. 421 * Replace the local ICs by their symmetric difference 422 * with the globally implied ICs for this class; if this 423 * difference is empty, remove the local ICs altogether. 424 * <p> 425 * An empty local IC attribute is reserved to signal 426 * the unpacker to delete the attribute altogether, 427 * so a missing local IC attribute signals the unpacker 428 * to use the globally implied ICs changed. 429 */ 430 void minimizeLocalICs() { 431 List<InnerClass> diff = computeICdiff(); 432 List<InnerClass> actualICs = innerClasses; 433 List<InnerClass> localICs; // will be the diff, modulo edge cases 434 if (diff.isEmpty()) { 435 // No diff, so transmit no attribute. 436 localICs = null; 437 if (actualICs != null && actualICs.isEmpty()) { 438 // Odd case: No implied ICs, and a zero length attr. 439 // Do not support it directly. 440 if (verbose > 0) 441 Utils.log.info("Warning: Dropping empty InnerClasses attribute from "+this); 442 } 443 } else if (actualICs == null) { 444 // No local IC attribute, even though some are implied. 445 // Signal with trivial attribute. 446 localICs = Collections.emptyList(); 447 } else { 448 // Transmit a non-empty diff, which will create 449 // a local ICs attribute. 450 localICs = diff; 451 } 452 // Reduce the set to the symmetric difference. 453 setInnerClasses(localICs); 454 if (verbose > 1 && localICs != null) 455 Utils.log.fine("keeping local ICs in "+this+": "+localICs); 456 } 457 458 /** When unpacking, undo the effect of minimizeLocalICs. 459 * Must return negative if any IC tuples may have been deleted. 460 * Otherwise, return positive if any IC tuples were added. 461 */ 462 int expandLocalICs() { 463 List<InnerClass> localICs = innerClasses; 464 List<InnerClass> actualICs; 465 int changed; 466 if (localICs == null) { 467 // Diff was empty. (Common case.) 468 List<InnerClass> impliedICs = computeGloballyImpliedICs(); 469 if (impliedICs.isEmpty()) { 470 actualICs = null; 471 changed = 0; 472 } else { 473 actualICs = impliedICs; 474 changed = 1; // added more tuples 475 } 476 } else if (localICs.isEmpty()) { 477 // It was a non-empty diff, but the local ICs were absent. 478 actualICs = null; 479 changed = -1; // [] => null, no tuple change, force recomputation 480 } else { 481 // Non-trivial diff was transmitted. 482 actualICs = computeICdiff(); 483 // If we only added more ICs, return +1. 484 changed = actualICs.containsAll(localICs)? +1: -1; 485 } 486 setInnerClasses(actualICs); 487 return changed; 488 } 489 490 public abstract 491 class Member extends Attribute.Holder implements Comparable<Member> { 492 DescriptorEntry descriptor; 493 494 protected Member(int flags, DescriptorEntry descriptor) { 495 this.flags = flags; 496 this.descriptor = descriptor; 497 } 498 499 public Class thisClass() { return Class.this; } 500 501 public DescriptorEntry getDescriptor() { 502 return descriptor; 503 } 504 public String getName() { 505 return descriptor.nameRef.stringValue(); 506 } 507 public String getType() { 508 return descriptor.typeRef.stringValue(); 509 } 510 511 protected Entry[] getCPMap() { 512 return cpMap; 513 } 514 protected void visitRefs(int mode, Collection<Entry> refs) { 515 if (verbose > 2) Utils.log.fine("visitRefs "+this); 516 // Careful: The descriptor is used by the package, 517 // but the classfile breaks it into component refs. 518 if (mode == VRM_CLASSIC) { 519 refs.add(descriptor.nameRef); 520 refs.add(descriptor.typeRef); 521 } else { 522 refs.add(descriptor); 523 } 524 // Handle attribute list: 525 super.visitRefs(mode, refs); 526 } 527 528 public String toString() { 529 return Class.this + "." + descriptor.prettyString(); 530 } 531 } 532 533 public 534 class Field extends Member { 535 // Order is significant for fields: It is visible to reflection. 536 int order; 537 538 public Field(int flags, DescriptorEntry descriptor) { 539 super(flags, descriptor); 540 assert(!descriptor.isMethod()); 541 if (fields == null) 542 fields = new ArrayList<>(); 543 boolean added = fields.add(this); 544 assert(added); 545 order = fields.size(); 546 } 547 548 public byte getLiteralTag() { 549 return descriptor.getLiteralTag(); 550 } 551 552 public int compareTo(Member o) { 553 Field that = (Field)o; 554 return this.order - that.order; 555 } 556 } 557 558 public 559 class Method extends Member { 560 // Code attribute is specially hardwired. 561 Code code; 562 563 public Method(int flags, DescriptorEntry descriptor) { 564 super(flags, descriptor); 565 assert(descriptor.isMethod()); 566 if (methods == null) 567 methods = new ArrayList<>(); 568 boolean added = methods.add(this); 569 assert(added); 570 } 571 572 public void trimToSize() { 573 super.trimToSize(); 574 if (code != null) 575 code.trimToSize(); 576 } 577 578 public int getArgumentSize() { 579 int argSize = descriptor.typeRef.computeSize(true); 580 int thisSize = Modifier.isStatic(flags) ? 0 : 1; 581 return thisSize + argSize; 582 } 583 584 // Sort methods in a canonical order (by type, then by name). 585 public int compareTo(Member o) { 586 Method that = (Method)o; 587 return this.getDescriptor().compareTo(that.getDescriptor()); 588 } 589 590 public void strip(String attrName) { 591 if ("Code".equals(attrName)) 592 code = null; 593 if (code != null) 594 code.strip(attrName); 595 super.strip(attrName); 596 } 597 protected void visitRefs(int mode, Collection<Entry> refs) { 598 super.visitRefs(mode, refs); 599 if (code != null) { 600 if (mode == VRM_CLASSIC) { 601 refs.add(getRefString("Code")); 602 } 603 code.visitRefs(mode, refs); 604 } 605 } 606 } 607 608 public void trimToSize() { 609 super.trimToSize(); 610 for (int isM = 0; isM <= 1; isM++) { 611 ArrayList<? extends Member> members = (isM == 0) ? fields : methods; 612 if (members == null) continue; 613 members.trimToSize(); 614 for (Member m : members) { 615 m.trimToSize(); 616 } 617 } 618 if (innerClasses != null) { 619 innerClasses.trimToSize(); 620 } 621 } 622 623 public void strip(String attrName) { 624 if ("InnerClass".equals(attrName)) 625 innerClasses = null; 626 for (int isM = 0; isM <= 1; isM++) { 627 ArrayList<? extends Member> members = (isM == 0) ? fields : methods; 628 if (members == null) continue; 629 for (Member m : members) { 630 m.strip(attrName); 631 } 632 } 633 super.strip(attrName); 634 } 635 636 protected void visitRefs(int mode, Collection<Entry> refs) { 637 if (verbose > 2) Utils.log.fine("visitRefs "+this); 638 refs.add(thisClass); 639 refs.add(superClass); 640 refs.addAll(Arrays.asList(interfaces)); 641 for (int isM = 0; isM <= 1; isM++) { 642 ArrayList<? extends Member> members = (isM == 0) ? fields : methods; 643 if (members == null) continue; 644 for (Member m : members) { 645 boolean ok = false; 646 try { 647 m.visitRefs(mode, refs); 648 ok = true; 649 } finally { 650 if (!ok) 651 Utils.log.warning("Error scanning "+m); 652 } 653 } 654 } 655 visitInnerClassRefs(mode, refs); 656 // Handle attribute list: 657 super.visitRefs(mode, refs); 658 } 659 660 protected void visitInnerClassRefs(int mode, Collection<Entry> refs) { 661 Package.visitInnerClassRefs(innerClasses, mode, refs); 662 } 663 664 // Hook called by ClassReader when it's done. 665 void finishReading() { 666 trimToSize(); 667 maybeChooseFileName(); 668 } 669 670 public void initFile(File file) { 671 assert(this.file == null); // set-once 672 if (file == null) { 673 // Build a trivial stub. 674 file = newStub(canonicalFileName()); 675 } 676 this.file = file; 677 assert(file.isClassStub()); 678 file.stubClass = this; 679 maybeChooseFileName(); 680 } 681 682 public void maybeChooseFileName() { 683 if (thisClass == null) { 684 return; // do not choose yet 685 } 686 String canonName = canonicalFileName(); 687 if (file.nameString.equals("")) { 688 file.nameString = canonName; 689 } 690 if (file.nameString.equals(canonName)) { 691 // The file name is predictable. Transmit "". 692 file.name = getRefString(""); 693 return; 694 } 695 // If name has not yet been looked up, find it now. 696 if (file.name == null) { 697 file.name = getRefString(file.nameString); 698 } 699 } 700 701 public String canonicalFileName() { 702 if (thisClass == null) return null; 703 return thisClass.stringValue() + ".class"; 704 } 705 706 public java.io.File getFileName(java.io.File parent) { 707 String name = file.name.stringValue(); 708 if (name.equals("")) 709 name = canonicalFileName(); 710 String fname = name.replace('/', java.io.File.separatorChar); 711 return new java.io.File(parent, fname); 712 } 713 public java.io.File getFileName() { 714 return getFileName(null); 715 } 716 717 public String toString() { 718 return thisClass.stringValue(); 719 } 720 } 721 722 void addClass(Class c) { 723 assert(c.getPackage() == this); 724 boolean added = classes.add(c); 725 assert(added); 726 // Make sure the class is represented in the total file order: 727 if (c.file == null) c.initFile(null); 728 addFile(c.file); 729 } 730 731 // What non-class files are in this unit? 732 ArrayList<File> files = new ArrayList<>(); 733 734 public List<File> getFiles() { 735 return files; 736 } 737 738 public List<File> getClassStubs() { 739 List<File> classStubs = new ArrayList<>(classes.size()); 740 for (Class cls : classes) { 741 assert(cls.file.isClassStub()); 742 classStubs.add(cls.file); 743 } 744 return classStubs; 745 } 746 747 public final class File implements Comparable<File> { 748 String nameString; // true name of this file 749 Utf8Entry name; 750 int modtime = NO_MODTIME; 751 int options = 0; // random flag bits, such as deflate_hint 752 Class stubClass; // if this is a stub, here's the class 753 ArrayList<byte[]> prepend = new ArrayList<>(); // list of byte[] 754 java.io.ByteArrayOutputStream append = new ByteArrayOutputStream(); 755 756 File(Utf8Entry name) { 757 this.name = name; 758 this.nameString = name.stringValue(); 759 // caller must fill in contents 760 } 761 File(String nameString) { 762 nameString = fixupFileName(nameString); 763 this.name = getRefString(nameString); 764 this.nameString = name.stringValue(); 765 } 766 767 public boolean isDirectory() { 768 // JAR directory. Useless. 769 return nameString.endsWith("/"); 770 } 771 public boolean isClassStub() { 772 return (options & FO_IS_CLASS_STUB) != 0; 773 } 774 public Class getStubClass() { 775 assert(isClassStub()); 776 assert(stubClass != null); 777 return stubClass; 778 } 779 public boolean isTrivialClassStub() { 780 return isClassStub() 781 && name.stringValue().equals("") 782 && (modtime == NO_MODTIME || modtime == default_modtime) 783 && (options &~ FO_IS_CLASS_STUB) == 0; 784 } 785 786 // The nameString is the key. Ignore other things. 787 // (Note: The name might be "", in the case of a trivial class stub.) 788 public boolean equals(Object o) { 789 if (o == null || (o.getClass() != File.class)) 790 return false; 791 File that = (File)o; 792 return that.nameString.equals(this.nameString); 793 } 794 public int hashCode() { 795 return nameString.hashCode(); 796 } 797 // Simple alphabetic sort. PackageWriter uses a better comparator. 798 public int compareTo(File that) { 799 return this.nameString.compareTo(that.nameString); 800 } 801 public String toString() { 802 return nameString+"{" 803 +(isClassStub()?"*":"") 804 +(BandStructure.testBit(options,FO_DEFLATE_HINT)?"@":"") 805 +(modtime==NO_MODTIME?"":"M"+modtime) 806 +(getFileLength()==0?"":"["+getFileLength()+"]") 807 +"}"; 808 } 809 810 public java.io.File getFileName() { 811 return getFileName(null); 812 } 813 public java.io.File getFileName(java.io.File parent) { 814 String lname = this.nameString; 815 //if (name.startsWith("./")) name = name.substring(2); 816 String fname = lname.replace('/', java.io.File.separatorChar); 817 return new java.io.File(parent, fname); 818 } 819 820 public void addBytes(byte[] bytes) { 821 addBytes(bytes, 0, bytes.length); 822 } 823 public void addBytes(byte[] bytes, int off, int len) { 824 if (((append.size() | len) << 2) < 0) { 825 prepend.add(append.toByteArray()); 826 append.reset(); 827 } 828 append.write(bytes, off, len); 829 } 830 public long getFileLength() { 831 long len = 0; 832 if (prepend == null || append == null) return 0; 833 for (byte[] block : prepend) { 834 len += block.length; 835 } 836 len += append.size(); 837 return len; 838 } 839 public void writeTo(OutputStream out) throws IOException { 840 if (prepend == null || append == null) return; 841 for (byte[] block : prepend) { 842 out.write(block); 843 } 844 append.writeTo(out); 845 } 846 public void readFrom(InputStream in) throws IOException { 847 byte[] buf = new byte[1 << 16]; 848 int nr; 849 while ((nr = in.read(buf)) > 0) { 850 addBytes(buf, 0, nr); 851 } 852 } 853 public InputStream getInputStream() { 854 InputStream in = new ByteArrayInputStream(append.toByteArray()); 855 if (prepend.isEmpty()) return in; 856 List<InputStream> isa = new ArrayList<>(prepend.size()+1); 857 for (byte[] bytes : prepend) { 858 isa.add(new ByteArrayInputStream(bytes)); 859 } 860 isa.add(in); 861 return new SequenceInputStream(Collections.enumeration(isa)); 862 } 863 864 protected void visitRefs(int mode, Collection<Entry> refs) { 865 assert(name != null); 866 refs.add(name); 867 } 868 } 869 870 File newStub(String classFileNameString) { 871 File stub = new File(classFileNameString); 872 stub.options |= FO_IS_CLASS_STUB; 873 stub.prepend = null; 874 stub.append = null; // do not collect data 875 return stub; 876 } 877 878 private static String fixupFileName(String name) { 879 String fname = name.replace(java.io.File.separatorChar, '/'); 880 if (fname.startsWith("/")) { 881 throw new IllegalArgumentException("absolute file name "+fname); 882 } 883 return fname; 884 } 885 886 void addFile(File file) { 887 boolean added = files.add(file); 888 assert(added); 889 } 890 891 // Is there a globally declared table of inner classes? 892 List<InnerClass> allInnerClasses = new ArrayList<>(); 893 Map<ClassEntry, InnerClass> allInnerClassesByThis; 894 895 public 896 List<InnerClass> getAllInnerClasses() { 897 return allInnerClasses; 898 } 899 900 public 901 void setAllInnerClasses(Collection<InnerClass> ics) { 902 assert(ics != allInnerClasses); 903 allInnerClasses.clear(); 904 allInnerClasses.addAll(ics); 905 906 // Make an index: 907 allInnerClassesByThis = new HashMap<>(allInnerClasses.size()); 908 for (InnerClass ic : allInnerClasses) { 909 Object pic = allInnerClassesByThis.put(ic.thisClass, ic); 910 assert(pic == null); // caller must ensure key uniqueness! 911 } 912 } 913 914 /** Return a global inner class record for the given thisClass. */ 915 public 916 InnerClass getGlobalInnerClass(Entry thisClass) { 917 assert(thisClass instanceof ClassEntry); 918 return allInnerClassesByThis.get(thisClass); 919 } 920 921 static 922 class InnerClass implements Comparable<InnerClass> { 923 final ClassEntry thisClass; 924 final ClassEntry outerClass; 925 final Utf8Entry name; 926 final int flags; 927 928 // Can name and outerClass be derived from thisClass? 929 final boolean predictable; 930 931 // About 30% of inner classes are anonymous (in rt.jar). 932 // About 60% are class members; the rest are named locals. 933 // Nearly all have predictable outers and names. 934 935 InnerClass(ClassEntry thisClass, ClassEntry outerClass, 936 Utf8Entry name, int flags) { 937 this.thisClass = thisClass; 938 this.outerClass = outerClass; 939 this.name = name; 940 this.flags = flags; 941 this.predictable = computePredictable(); 942 } 943 944 private boolean computePredictable() { 945 //System.out.println("computePredictable "+outerClass+" "+this.name); 946 String[] parse = parseInnerClassName(thisClass.stringValue()); 947 if (parse == null) return false; 948 String pkgOuter = parse[0]; 949 //String number = parse[1]; 950 String lname = parse[2]; 951 String haveName = (this.name == null) ? null : this.name.stringValue(); 952 String haveOuter = (outerClass == null) ? null : outerClass.stringValue(); 953 boolean lpredictable = (lname == haveName && pkgOuter == haveOuter); 954 //System.out.println("computePredictable => "+predictable); 955 return lpredictable; 956 } 957 958 public boolean equals(Object o) { 959 if (o == null || o.getClass() != InnerClass.class) 960 return false; 961 InnerClass that = (InnerClass)o; 962 return eq(this.thisClass, that.thisClass) 963 && eq(this.outerClass, that.outerClass) 964 && eq(this.name, that.name) 965 && this.flags == that.flags; 966 } 967 private static boolean eq(Object x, Object y) { 968 return (x == null)? y == null: x.equals(y); 969 } 970 public int hashCode() { 971 return thisClass.hashCode(); 972 } 973 public int compareTo(InnerClass that) { 974 return this.thisClass.compareTo(that.thisClass); 975 } 976 977 protected void visitRefs(int mode, Collection<Entry> refs) { 978 refs.add(thisClass); 979 if (mode == VRM_CLASSIC || !predictable) { 980 // If the name can be demangled, the package omits 981 // the products of demangling. Otherwise, include them. 982 refs.add(outerClass); 983 refs.add(name); 984 } 985 } 986 987 public String toString() { 988 return thisClass.stringValue(); 989 } 990 } 991 992 // Helper for building InnerClasses attributes. 993 private static 994 void visitInnerClassRefs(Collection<InnerClass> innerClasses, int mode, Collection<Entry> refs) { 995 if (innerClasses == null) { 996 return; // no attribute; nothing to do 997 } 998 if (mode == VRM_CLASSIC) { 999 refs.add(getRefString("InnerClasses")); 1000 } 1001 if (innerClasses.size() > 0) { 1002 // Count the entries themselves: 1003 for (InnerClass c : innerClasses) { 1004 c.visitRefs(mode, refs); 1005 } 1006 } 1007 } 1008 1009 static String[] parseInnerClassName(String n) { 1010 //System.out.println("parseInnerClassName "+n); 1011 String pkgOuter, number, name; 1012 int dollar1, dollar2; // pointers to $ in the pattern 1013 // parse n = (<pkg>/)*<outer>($<number>)?($<name>)? 1014 int nlen = n.length(); 1015 int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; 1016 dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, n.length()); 1017 if (dollar2 < pkglen) return null; 1018 if (isDigitString(n, dollar2+1, nlen)) { 1019 // n = (<pkg>/)*<outer>$<number> 1020 number = n.substring(dollar2+1, nlen); 1021 name = null; 1022 dollar1 = dollar2; 1023 } else if ((dollar1 1024 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2-1)) 1025 > pkglen 1026 && isDigitString(n, dollar1+1, dollar2)) { 1027 // n = (<pkg>/)*<outer>$<number>$<name> 1028 number = n.substring(dollar1+1, dollar2); 1029 name = n.substring(dollar2+1, nlen).intern(); 1030 } else { 1031 // n = (<pkg>/)*<outer>$<name> 1032 dollar1 = dollar2; 1033 number = null; 1034 name = n.substring(dollar2+1, nlen).intern(); 1035 } 1036 if (number == null) 1037 pkgOuter = n.substring(0, dollar1).intern(); 1038 else 1039 pkgOuter = null; 1040 //System.out.println("parseInnerClassName parses "+pkgOuter+" "+number+" "+name); 1041 return new String[] { pkgOuter, number, name }; 1042 } 1043 1044 private static final int SLASH_MIN = '.'; 1045 private static final int SLASH_MAX = '/'; 1046 private static final int DOLLAR_MIN = 0; 1047 private static final int DOLLAR_MAX = '-'; 1048 static { 1049 assert(lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, "x$$y$", 4) == 2); 1050 assert(lastIndexOf(SLASH_MIN, SLASH_MAX, "x//y/", 4) == 2); 1051 } 1052 1053 private static int lastIndexOf(int chMin, int chMax, String str, int pos) { 1054 for (int i = pos; --i >= 0; ) { 1055 int ch = str.charAt(i); 1056 if (ch >= chMin && ch <= chMax) { 1057 return i; 1058 } 1059 } 1060 return -1; 1061 } 1062 1063 private static boolean isDigitString(String x, int beg, int end) { 1064 if (beg == end) return false; // null string 1065 for (int i = beg; i < end; i++) { 1066 char ch = x.charAt(i); 1067 if (!(ch >= '0' && ch <= '9')) return false; 1068 } 1069 return true; 1070 } 1071 1072 static String getObviousSourceFile(String className) { 1073 String n = className; 1074 int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; 1075 n = n.substring(pkglen); 1076 int cutoff = n.length(); 1077 for (;;) { 1078 // Work backwards, finding all '$', '#', etc. 1079 int dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, cutoff-1); 1080 if (dollar2 < 0) 1081 break; 1082 cutoff = dollar2; 1083 if (cutoff == 0) 1084 break; 1085 } 1086 String obvious = n.substring(0, cutoff)+".java"; 1087 return obvious; 1088 } 1089 /* 1090 static { 1091 assert(getObviousSourceFile("foo").equals("foo.java")); 1092 assert(getObviousSourceFile("foo/bar").equals("bar.java")); 1093 assert(getObviousSourceFile("foo/bar$baz").equals("bar.java")); 1094 assert(getObviousSourceFile("foo/bar#baz#1").equals("bar.java")); 1095 assert(getObviousSourceFile("foo.bar.baz#1").equals("baz.java")); 1096 } 1097 */ 1098 1099 static Utf8Entry getRefString(String s) { 1100 return ConstantPool.getUtf8Entry(s); 1101 } 1102 1103 static LiteralEntry getRefLiteral(Comparable<?> s) { 1104 return ConstantPool.getLiteralEntry(s); 1105 } 1106 1107 void stripAttributeKind(String what) { 1108 // what is one of { Debug, Compile, Constant, Exceptions, InnerClasses } 1109 if (verbose > 0) 1110 Utils.log.info("Stripping "+what.toLowerCase()+" data and attributes..."); 1111 switch (what) { 1112 case "Debug": 1113 strip("SourceFile"); 1114 strip("LineNumberTable"); 1115 strip("LocalVariableTable"); 1116 strip("LocalVariableTypeTable"); 1117 break; 1118 case "Compile": 1119 // Keep the inner classes normally. 1120 // Although they have no effect on execution, 1121 // the Reflection API exposes them, and JCK checks them. 1122 // NO: // strip("InnerClasses"); 1123 strip("Deprecated"); 1124 strip("Synthetic"); 1125 break; 1126 case "Exceptions": 1127 // Keep the exceptions normally. 1128 // Although they have no effect on execution, 1129 // the Reflection API exposes them, and JCK checks them. 1130 strip("Exceptions"); 1131 break; 1132 case "Constant": 1133 stripConstantFields(); 1134 break; 1135 } 1136 } 1137 1138 public void trimToSize() { 1139 classes.trimToSize(); 1140 for (Class c : classes) { 1141 c.trimToSize(); 1142 } 1143 files.trimToSize(); 1144 } 1145 1146 public void strip(String attrName) { 1147 for (Class c : classes) { 1148 c.strip(attrName); 1149 } 1150 } 1151 1152 public void stripConstantFields() { 1153 for (Class c : classes) { 1154 for (Iterator<Class.Field> j = c.fields.iterator(); j.hasNext(); ) { 1155 Class.Field f = j.next(); 1156 if (Modifier.isFinal(f.flags) 1157 // do not strip non-static finals: 1158 && Modifier.isStatic(f.flags) 1159 && f.getAttribute("ConstantValue") != null 1160 && !f.getName().startsWith("serial")) { 1161 if (verbose > 2) { 1162 Utils.log.fine(">> Strip "+this+" ConstantValue"); 1163 j.remove(); 1164 } 1165 } 1166 } 1167 } 1168 } 1169 1170 protected void visitRefs(int mode, Collection<Entry> refs) { 1171 for ( Class c : classes) { 1172 c.visitRefs(mode, refs); 1173 } 1174 if (mode != VRM_CLASSIC) { 1175 for (File f : files) { 1176 f.visitRefs(mode, refs); 1177 } 1178 visitInnerClassRefs(allInnerClasses, mode, refs); 1179 } 1180 } 1181 1182 // Use this before writing the package file. 1183 // It sorts files into a new order which seems likely to 1184 // compress better. It also moves classes to the end of the 1185 // file order. It also removes JAR directory entries, which 1186 // are useless. 1187 void reorderFiles(boolean keepClassOrder, boolean stripDirectories) { 1188 // First reorder the classes, if that is allowed. 1189 if (!keepClassOrder) { 1190 // In one test with rt.jar, this trick gained 0.7% 1191 Collections.sort(classes); 1192 } 1193 1194 // Remove stubs from resources; maybe we'll add them on at the end, 1195 // if there are some non-trivial ones. The best case is that 1196 // modtimes and options are not transmitted, and the stub files 1197 // for class files do not need to be transmitted at all. 1198 // Also 1199 List<File> stubs = getClassStubs(); 1200 for (Iterator<File> i = files.iterator(); i.hasNext(); ) { 1201 File file = i.next(); 1202 if (file.isClassStub() || 1203 (stripDirectories && file.isDirectory())) { 1204 i.remove(); 1205 } 1206 } 1207 1208 // Sort the remaining non-class files. 1209 // We sort them by file type. 1210 // This keeps files of similar format near each other. 1211 // Put class files at the end, keeping their fixed order. 1212 // Be sure the JAR file's required manifest stays at the front. (4893051) 1213 Collections.sort(files, new Comparator<>() { 1214 public int compare(File r0, File r1) { 1215 // Get the file name. 1216 String f0 = r0.nameString; 1217 String f1 = r1.nameString; 1218 if (f0.equals(f1)) return 0; 1219 if (JarFile.MANIFEST_NAME.equals(f0)) return 0-1; 1220 if (JarFile.MANIFEST_NAME.equals(f1)) return 1-0; 1221 // Extract file basename. 1222 String n0 = f0.substring(1+f0.lastIndexOf('/')); 1223 String n1 = f1.substring(1+f1.lastIndexOf('/')); 1224 // Extract basename extension. 1225 String x0 = n0.substring(1+n0.lastIndexOf('.')); 1226 String x1 = n1.substring(1+n1.lastIndexOf('.')); 1227 int r; 1228 // Primary sort key is file extension. 1229 r = x0.compareTo(x1); 1230 if (r != 0) return r; 1231 r = f0.compareTo(f1); 1232 return r; 1233 } 1234 }); 1235 1236 // Add back the class stubs after sorting, before trimStubs. 1237 files.addAll(stubs); 1238 } 1239 1240 void trimStubs() { 1241 // Restore enough non-trivial stubs to carry the needed class modtimes. 1242 for (ListIterator<File> i = files.listIterator(files.size()); i.hasPrevious(); ) { 1243 File file = i.previous(); 1244 if (!file.isTrivialClassStub()) { 1245 if (verbose > 1) 1246 Utils.log.fine("Keeping last non-trivial "+file); 1247 break; 1248 } 1249 if (verbose > 2) 1250 Utils.log.fine("Removing trivial "+file); 1251 i.remove(); 1252 } 1253 1254 if (verbose > 0) { 1255 Utils.log.info("Transmitting "+files.size()+" files, including per-file data for "+getClassStubs().size()+" classes out of "+classes.size()); 1256 } 1257 } 1258 1259 // Use this before writing the package file. 1260 void buildGlobalConstantPool(Set<Entry> requiredEntries) { 1261 if (verbose > 1) 1262 Utils.log.fine("Checking for unused CP entries"); 1263 requiredEntries.add(getRefString("")); // uconditionally present 1264 visitRefs(VRM_PACKAGE, requiredEntries); 1265 ConstantPool.completeReferencesIn(requiredEntries, false); 1266 if (verbose > 1) 1267 Utils.log.fine("Sorting CP entries"); 1268 Index cpAllU = ConstantPool.makeIndex("unsorted", requiredEntries); 1269 Index[] byTagU = ConstantPool.partitionByTag(cpAllU); 1270 for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { 1271 byte tag = ConstantPool.TAGS_IN_ORDER[i]; 1272 // Work on all entries of a given kind. 1273 Index ix = byTagU[tag]; 1274 if (ix == null) continue; 1275 ConstantPool.sort(ix); 1276 cp.initIndexByTag(tag, ix); 1277 byTagU[tag] = null; // done with it 1278 } 1279 for (int i = 0; i < byTagU.length; i++) { 1280 Index ix = byTagU[i]; 1281 assert(ix == null); // all consumed 1282 } 1283 for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { 1284 byte tag = ConstantPool.TAGS_IN_ORDER[i]; 1285 Index ix = cp.getIndexByTag(tag); 1286 assert(ix.assertIsSorted()); 1287 if (verbose > 2) Utils.log.fine(ix.dumpString()); 1288 } 1289 } 1290 1291 // Use this before writing the class files. 1292 void ensureAllClassFiles() { 1293 Set<File> fileSet = new HashSet<>(files); 1294 for (Class cls : classes) { 1295 // Add to the end of ths list: 1296 if (!fileSet.contains(cls.file)) 1297 files.add(cls.file); 1298 } 1299 } 1300 1301 static final List<Object> noObjects = Arrays.asList(new Object[0]); 1302 static final List<Class.Field> noFields = Arrays.asList(new Class.Field[0]); 1303 static final List<Class.Method> noMethods = Arrays.asList(new Class.Method[0]); 1304 static final List<InnerClass> noInnerClasses = Arrays.asList(new InnerClass[0]); 1305 1306 protected static final class Version { 1307 1308 public final short major; 1309 public final short minor; 1310 1311 private Version(short major, short minor) { 1312 this.major = major; 1313 this.minor = minor; 1314 } 1315 1316 public String toString() { 1317 return major + "." + minor; 1318 } 1319 1320 public boolean equals(Object that) { 1321 return that instanceof Version 1322 && major == ((Version)that).major 1323 && minor == ((Version)that).minor; 1324 } 1325 1326 public int intValue() { 1327 return (major << 16) + minor; 1328 } 1329 1330 public int hashCode() { 1331 return (major << 16) + 7 + minor; 1332 } 1333 1334 public static Version of(int major, int minor) { 1335 return new Version((short)major, (short)minor); 1336 } 1337 1338 public static Version of(byte[] bytes) { 1339 int minor = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF); 1340 int major = ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); 1341 return new Version((short)major, (short)minor); 1342 } 1343 1344 public static Version of(int major_minor) { 1345 short minor = (short)major_minor; 1346 short major = (short)(major_minor >>> 16); 1347 return new Version(major, minor); 1348 } 1349 1350 public static Version makeVersion(PropMap props, String partialKey) { 1351 int min = props.getInteger(Utils.COM_PREFIX 1352 + partialKey + ".minver", -1); 1353 int maj = props.getInteger(Utils.COM_PREFIX 1354 + partialKey + ".majver", -1); 1355 return min >= 0 && maj >= 0 ? Version.of(maj, min) : null; 1356 } 1357 public byte[] asBytes() { 1358 byte[] bytes = { 1359 (byte) (minor >> 8), (byte) minor, 1360 (byte) (major >> 8), (byte) major 1361 }; 1362 return bytes; 1363 } 1364 public int compareTo(Version that) { 1365 return this.intValue() - that.intValue(); 1366 } 1367 1368 public boolean lessThan(Version that) { 1369 return compareTo(that) < 0 ; 1370 } 1371 1372 public boolean greaterThan(Version that) { 1373 return compareTo(that) > 0 ; 1374 } 1375 } 1376 }