1 /* 2 * Copyright (C) 2007 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 17 package com.android.dx.cf.direct; 18 19 import com.android.dx.cf.attrib.AttBootstrapMethods; 20 import com.android.dx.cf.attrib.AttSourceFile; 21 import com.android.dx.cf.code.BootstrapMethodsList; 22 import com.android.dx.cf.cst.ConstantPoolParser; 23 import com.android.dx.cf.iface.Attribute; 24 import com.android.dx.cf.iface.AttributeList; 25 import com.android.dx.cf.iface.ClassFile; 26 import com.android.dx.cf.iface.FieldList; 27 import com.android.dx.cf.iface.MethodList; 28 import com.android.dx.cf.iface.ParseException; 29 import com.android.dx.cf.iface.ParseObserver; 30 import com.android.dx.cf.iface.StdAttributeList; 31 import com.android.dx.rop.code.AccessFlags; 32 import com.android.dx.rop.cst.ConstantPool; 33 import com.android.dx.rop.cst.CstString; 34 import com.android.dx.rop.cst.CstType; 35 import com.android.dx.rop.cst.StdConstantPool; 36 import com.android.dx.rop.type.StdTypeList; 37 import com.android.dx.rop.type.Type; 38 import com.android.dx.rop.type.TypeList; 39 import com.android.dx.util.ByteArray; 40 import com.android.dx.util.Hex; 41 42 /** 43 * Class file with info taken from a {@code byte[]} or slice thereof. 44 */ 45 public class DirectClassFile implements ClassFile { 46 /** the expected value of the ClassFile.magic field */ 47 private static final int CLASS_FILE_MAGIC = 0xcafebabe; 48 49 /** 50 * minimum {@code .class} file major version 51 * 52 * See http://en.wikipedia.org/wiki/Java_class_file for an up-to-date 53 * list of version numbers. Currently known (taken from that table) are: 54 * 55 * Java SE 9 = 53 (0x35 hex), 56 * Java SE 8 = 52 (0x34 hex), 57 * Java SE 7 = 51 (0x33 hex), 58 * Java SE 6.0 = 50 (0x32 hex), 59 * Java SE 5.0 = 49 (0x31 hex), 60 * JDK 1.4 = 48 (0x30 hex), 61 * JDK 1.3 = 47 (0x2F hex), 62 * JDK 1.2 = 46 (0x2E hex), 63 * JDK 1.1 = 45 (0x2D hex). 64 * 65 * Valid ranges are typically of the form 66 * "A.0 through B.C inclusive" where A <= B and C >= 0, 67 * which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION. 68 */ 69 private static final int CLASS_FILE_MIN_MAJOR_VERSION = 45; 70 71 /** 72 * maximum {@code .class} file major version 73 * 74 * Note: if you change this, please change "java.class.version" in System.java. 75 */ 76 private static final int CLASS_FILE_MAX_MAJOR_VERSION = 53; 77 78 /** maximum {@code .class} file minor version */ 79 private static final int CLASS_FILE_MAX_MINOR_VERSION = 0; 80 81 /** 82 * {@code non-null;} the file path for the class, excluding any base directory 83 * specification 84 */ 85 private final String filePath; 86 87 /** {@code non-null;} the bytes of the file */ 88 private final ByteArray bytes; 89 90 /** 91 * whether to be strict about parsing; if 92 * {@code false}, this avoids doing checks that only exist 93 * for purposes of verification (such as magic number matching and 94 * path-package consistency checking) 95 */ 96 private final boolean strictParse; 97 98 /** 99 * {@code null-ok;} the constant pool; only ever {@code null} 100 * before the constant pool is successfully parsed 101 */ 102 private StdConstantPool pool; 103 104 /** 105 * the class file field {@code access_flags}; will be {@code -1} 106 * before the file is successfully parsed 107 */ 108 private int accessFlags; 109 110 /** 111 * {@code null-ok;} the class file field {@code this_class}, 112 * interpreted as a type constant; only ever {@code null} 113 * before the file is successfully parsed 114 */ 115 private CstType thisClass; 116 117 /** 118 * {@code null-ok;} the class file field {@code super_class}, interpreted 119 * as a type constant if non-zero 120 */ 121 private CstType superClass; 122 123 /** 124 * {@code null-ok;} the class file field {@code interfaces}; only 125 * ever {@code null} before the file is successfully 126 * parsed 127 */ 128 private TypeList interfaces; 129 130 /** 131 * {@code null-ok;} the class file field {@code fields}; only ever 132 * {@code null} before the file is successfully parsed 133 */ 134 private FieldList fields; 135 136 /** 137 * {@code null-ok;} the class file field {@code methods}; only ever 138 * {@code null} before the file is successfully parsed 139 */ 140 private MethodList methods; 141 142 /** 143 * {@code null-ok;} the class file field {@code attributes}; only 144 * ever {@code null} before the file is successfully 145 * parsed 146 */ 147 private StdAttributeList attributes; 148 149 /** {@code null-ok;} attribute factory, if any */ 150 private AttributeFactory attributeFactory; 151 152 /** {@code null-ok;} parse observer, if any */ 153 private ParseObserver observer; 154 155 /** 156 * Returns the string form of an object or {@code "(none)"} 157 * (rather than {@code "null"}) for {@code null}. 158 * 159 * @param obj {@code null-ok;} the object to stringify 160 * @return {@code non-null;} the appropriate string form 161 */ stringOrNone(Object obj)162 public static String stringOrNone(Object obj) { 163 if (obj == null) { 164 return "(none)"; 165 } 166 167 return obj.toString(); 168 } 169 170 /** 171 * Constructs an instance. 172 * 173 * @param bytes {@code non-null;} the bytes of the file 174 * @param filePath {@code non-null;} the file path for the class, 175 * excluding any base directory specification 176 * @param strictParse whether to be strict about parsing; if 177 * {@code false}, this avoids doing checks that only exist 178 * for purposes of verification (such as magic number matching and 179 * path-package consistency checking) 180 */ DirectClassFile(ByteArray bytes, String filePath, boolean strictParse)181 public DirectClassFile(ByteArray bytes, String filePath, 182 boolean strictParse) { 183 if (bytes == null) { 184 throw new NullPointerException("bytes == null"); 185 } 186 187 if (filePath == null) { 188 throw new NullPointerException("filePath == null"); 189 } 190 191 this.filePath = filePath; 192 this.bytes = bytes; 193 this.strictParse = strictParse; 194 this.accessFlags = -1; 195 } 196 197 /** 198 * Constructs an instance. 199 * 200 * @param bytes {@code non-null;} the bytes of the file 201 * @param filePath {@code non-null;} the file path for the class, 202 * excluding any base directory specification 203 * @param strictParse whether to be strict about parsing; if 204 * {@code false}, this avoids doing checks that only exist 205 * for purposes of verification (such as magic number matching and 206 * path-package consistency checking) 207 */ DirectClassFile(byte[] bytes, String filePath, boolean strictParse)208 public DirectClassFile(byte[] bytes, String filePath, 209 boolean strictParse) { 210 this(new ByteArray(bytes), filePath, strictParse); 211 } 212 213 /** 214 * Sets the parse observer for this instance. 215 * 216 * @param observer {@code null-ok;} the observer 217 */ setObserver(ParseObserver observer)218 public void setObserver(ParseObserver observer) { 219 this.observer = observer; 220 } 221 222 /** 223 * Sets the attribute factory to use. 224 * 225 * @param attributeFactory {@code non-null;} the attribute factory 226 */ setAttributeFactory(AttributeFactory attributeFactory)227 public void setAttributeFactory(AttributeFactory attributeFactory) { 228 if (attributeFactory == null) { 229 throw new NullPointerException("attributeFactory == null"); 230 } 231 232 this.attributeFactory = attributeFactory; 233 } 234 235 /** 236 * Gets the path where this class file is located. 237 * 238 * @return {@code non-null;} the filePath 239 */ getFilePath()240 public String getFilePath() { 241 return filePath; 242 } 243 244 /** 245 * Gets the {@link ByteArray} that this instance's data comes from. 246 * 247 * @return {@code non-null;} the bytes 248 */ getBytes()249 public ByteArray getBytes() { 250 return bytes; 251 } 252 253 /** {@inheritDoc} */ 254 @Override getMagic()255 public int getMagic() { 256 parseToInterfacesIfNecessary(); 257 return getMagic0(); 258 } 259 260 /** {@inheritDoc} */ 261 @Override getMinorVersion()262 public int getMinorVersion() { 263 parseToInterfacesIfNecessary(); 264 return getMinorVersion0(); 265 } 266 267 /** {@inheritDoc} */ 268 @Override getMajorVersion()269 public int getMajorVersion() { 270 parseToInterfacesIfNecessary(); 271 return getMajorVersion0(); 272 } 273 274 /** {@inheritDoc} */ 275 @Override getAccessFlags()276 public int getAccessFlags() { 277 parseToInterfacesIfNecessary(); 278 return accessFlags; 279 } 280 281 /** {@inheritDoc} */ 282 @Override getThisClass()283 public CstType getThisClass() { 284 parseToInterfacesIfNecessary(); 285 return thisClass; 286 } 287 288 /** {@inheritDoc} */ 289 @Override getSuperclass()290 public CstType getSuperclass() { 291 parseToInterfacesIfNecessary(); 292 return superClass; 293 } 294 295 /** {@inheritDoc} */ 296 @Override getConstantPool()297 public ConstantPool getConstantPool() { 298 parseToInterfacesIfNecessary(); 299 return pool; 300 } 301 302 /** {@inheritDoc} */ 303 @Override getInterfaces()304 public TypeList getInterfaces() { 305 parseToInterfacesIfNecessary(); 306 return interfaces; 307 } 308 309 /** {@inheritDoc} */ 310 @Override getFields()311 public FieldList getFields() { 312 parseToEndIfNecessary(); 313 return fields; 314 } 315 316 /** {@inheritDoc} */ 317 @Override getMethods()318 public MethodList getMethods() { 319 parseToEndIfNecessary(); 320 return methods; 321 } 322 323 /** {@inheritDoc} */ 324 @Override getAttributes()325 public AttributeList getAttributes() { 326 parseToEndIfNecessary(); 327 return attributes; 328 } 329 330 /** {@inheritDoc} */ 331 @Override getBootstrapMethods()332 public BootstrapMethodsList getBootstrapMethods() { 333 AttBootstrapMethods bootstrapMethodsAttribute = 334 (AttBootstrapMethods) getAttributes().findFirst(AttBootstrapMethods.ATTRIBUTE_NAME); 335 if (bootstrapMethodsAttribute != null) { 336 return bootstrapMethodsAttribute.getBootstrapMethods(); 337 } else { 338 return BootstrapMethodsList.EMPTY; 339 } 340 } 341 342 /** {@inheritDoc} */ 343 @Override getSourceFile()344 public CstString getSourceFile() { 345 AttributeList attribs = getAttributes(); 346 Attribute attSf = attribs.findFirst(AttSourceFile.ATTRIBUTE_NAME); 347 348 if (attSf instanceof AttSourceFile) { 349 return ((AttSourceFile) attSf).getSourceFile(); 350 } 351 352 return null; 353 } 354 355 /** 356 * Constructs and returns an instance of {@link TypeList} whose 357 * data comes from the bytes of this instance, interpreted as a 358 * list of constant pool indices for classes, which are in turn 359 * translated to type constants. Instance construction will fail 360 * if any of the (alleged) indices turn out not to refer to 361 * constant pool entries of type {@code Class}. 362 * 363 * @param offset offset into {@link #bytes} for the start of the 364 * data 365 * @param size number of elements in the list (not number of bytes) 366 * @return {@code non-null;} an appropriately-constructed class list 367 */ makeTypeList(int offset, int size)368 public TypeList makeTypeList(int offset, int size) { 369 if (size == 0) { 370 return StdTypeList.EMPTY; 371 } 372 373 if (pool == null) { 374 throw new IllegalStateException("pool not yet initialized"); 375 } 376 377 return new DcfTypeList(bytes, offset, size, pool, observer); 378 } 379 380 /** 381 * Gets the class file field {@code magic}, but without doing any 382 * checks or parsing first. 383 * 384 * @return the magic value 385 */ getMagic0()386 public int getMagic0() { 387 return bytes.getInt(0); 388 } 389 390 /** 391 * Gets the class file field {@code minor_version}, but 392 * without doing any checks or parsing first. 393 * 394 * @return the minor version 395 */ getMinorVersion0()396 public int getMinorVersion0() { 397 return bytes.getUnsignedShort(4); 398 } 399 400 /** 401 * Gets the class file field {@code major_version}, but 402 * without doing any checks or parsing first. 403 * 404 * @return the major version 405 */ getMajorVersion0()406 public int getMajorVersion0() { 407 return bytes.getUnsignedShort(6); 408 } 409 410 /** 411 * Runs {@link #parse} if it has not yet been run to cover up to 412 * the interfaces list. 413 */ parseToInterfacesIfNecessary()414 private void parseToInterfacesIfNecessary() { 415 if (accessFlags == -1) { 416 parse(); 417 } 418 } 419 420 /** 421 * Runs {@link #parse} if it has not yet been run successfully. 422 */ parseToEndIfNecessary()423 private void parseToEndIfNecessary() { 424 if (attributes == null) { 425 parse(); 426 } 427 } 428 429 /** 430 * Does the parsing, handing exceptions. 431 */ parse()432 private void parse() { 433 try { 434 parse0(); 435 } catch (ParseException ex) { 436 ex.addContext("...while parsing " + filePath); 437 throw ex; 438 } catch (RuntimeException ex) { 439 ParseException pe = new ParseException(ex); 440 pe.addContext("...while parsing " + filePath); 441 throw pe; 442 } 443 } 444 445 /** 446 * Sees if the .class file header magic has the good value. 447 * 448 * @param magic the value of a classfile "magic" field 449 * @return true if the magic is valid 450 */ isGoodMagic(int magic)451 private boolean isGoodMagic(int magic) { 452 return magic == CLASS_FILE_MAGIC; 453 } 454 455 /** 456 * Sees if the .class file header version are within 457 * range. 458 * 459 * @param minorVersion the value of a classfile "minor_version" field 460 * @param majorVersion the value of a classfile "major_version" field 461 * @return true if the parameters are valid and within range 462 */ isGoodVersion(int minorVersion, int majorVersion)463 private boolean isGoodVersion(int minorVersion, int majorVersion) { 464 /* Valid version ranges are typically of the form 465 * "A.0 through B.C inclusive" where A <= B and C >= 0, 466 * which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION. 467 */ 468 if (minorVersion >= 0) { 469 /* Check against max first to handle the case where 470 * MIN_MAJOR == MAX_MAJOR. 471 */ 472 if (majorVersion == CLASS_FILE_MAX_MAJOR_VERSION) { 473 if (minorVersion <= CLASS_FILE_MAX_MINOR_VERSION) { 474 return true; 475 } 476 } else if (majorVersion < CLASS_FILE_MAX_MAJOR_VERSION && 477 majorVersion >= CLASS_FILE_MIN_MAJOR_VERSION) { 478 return true; 479 } 480 } 481 482 return false; 483 } 484 485 /** 486 * Does the actual parsing. 487 */ parse0()488 private void parse0() { 489 if (bytes.size() < 10) { 490 throw new ParseException("severely truncated class file"); 491 } 492 493 if (observer != null) { 494 observer.parsed(bytes, 0, 0, "begin classfile"); 495 observer.parsed(bytes, 0, 4, "magic: " + Hex.u4(getMagic0())); 496 observer.parsed(bytes, 4, 2, 497 "minor_version: " + Hex.u2(getMinorVersion0())); 498 observer.parsed(bytes, 6, 2, 499 "major_version: " + Hex.u2(getMajorVersion0())); 500 } 501 502 if (strictParse) { 503 /* Make sure that this looks like a valid class file with a 504 * version that we can handle. 505 */ 506 if (!isGoodMagic(getMagic0())) { 507 throw new ParseException("bad class file magic (" + Hex.u4(getMagic0()) + ")"); 508 } 509 510 if (!isGoodVersion(getMinorVersion0(), getMajorVersion0())) { 511 throw new ParseException("unsupported class file version " + 512 getMajorVersion0() + "." + 513 getMinorVersion0()); 514 } 515 } 516 517 ConstantPoolParser cpParser = new ConstantPoolParser(bytes); 518 cpParser.setObserver(observer); 519 pool = cpParser.getPool(); 520 pool.setImmutable(); 521 522 int at = cpParser.getEndOffset(); 523 int accessFlags = bytes.getUnsignedShort(at); // u2 access_flags; 524 int cpi = bytes.getUnsignedShort(at + 2); // u2 this_class; 525 thisClass = (CstType) pool.get(cpi); 526 cpi = bytes.getUnsignedShort(at + 4); // u2 super_class; 527 superClass = (CstType) pool.get0Ok(cpi); 528 int count = bytes.getUnsignedShort(at + 6); // u2 interfaces_count 529 530 if (observer != null) { 531 observer.parsed(bytes, at, 2, 532 "access_flags: " + 533 AccessFlags.classString(accessFlags)); 534 observer.parsed(bytes, at + 2, 2, "this_class: " + thisClass); 535 observer.parsed(bytes, at + 4, 2, "super_class: " + 536 stringOrNone(superClass)); 537 observer.parsed(bytes, at + 6, 2, 538 "interfaces_count: " + Hex.u2(count)); 539 if (count != 0) { 540 observer.parsed(bytes, at + 8, 0, "interfaces:"); 541 } 542 } 543 544 at += 8; 545 interfaces = makeTypeList(at, count); 546 at += count * 2; 547 548 if (strictParse) { 549 /* 550 * Make sure that the file/jar path matches the declared 551 * package/class name. 552 */ 553 String thisClassName = thisClass.getClassType().getClassName(); 554 if (!(filePath.endsWith(".class") && 555 filePath.startsWith(thisClassName) && 556 (filePath.length() == (thisClassName.length() + 6)))) { 557 throw new ParseException("class name (" + thisClassName + 558 ") does not match path (" + 559 filePath + ")"); 560 } 561 } 562 563 /* 564 * Only set the instance variable accessFlags here, since 565 * that's what signals a successful parse of the first part of 566 * the file (through the interfaces list). 567 */ 568 this.accessFlags = accessFlags; 569 570 FieldListParser flParser = 571 new FieldListParser(this, thisClass, at, attributeFactory); 572 flParser.setObserver(observer); 573 fields = flParser.getList(); 574 at = flParser.getEndOffset(); 575 576 MethodListParser mlParser = 577 new MethodListParser(this, thisClass, at, attributeFactory); 578 mlParser.setObserver(observer); 579 methods = mlParser.getList(); 580 at = mlParser.getEndOffset(); 581 582 AttributeListParser alParser = 583 new AttributeListParser(this, AttributeFactory.CTX_CLASS, at, 584 attributeFactory); 585 alParser.setObserver(observer); 586 attributes = alParser.getList(); 587 attributes.setImmutable(); 588 at = alParser.getEndOffset(); 589 590 if (at != bytes.size()) { 591 throw new ParseException("extra bytes at end of class file, " + 592 "at offset " + Hex.u4(at)); 593 } 594 595 if (observer != null) { 596 observer.parsed(bytes, at, 0, "end classfile"); 597 } 598 } 599 600 /** 601 * Implementation of {@link TypeList} whose data comes directly 602 * from the bytes of an instance of this (outer) class, 603 * interpreted as a list of constant pool indices for classes 604 * which are in turn returned as type constants. Instance 605 * construction will fail if any of the (alleged) indices turn out 606 * not to refer to constant pool entries of type 607 * {@code Class}. 608 */ 609 private static class DcfTypeList implements TypeList { 610 /** {@code non-null;} array containing the data */ 611 private final ByteArray bytes; 612 613 /** number of elements in the list (not number of bytes) */ 614 private final int size; 615 616 /** {@code non-null;} the constant pool */ 617 private final StdConstantPool pool; 618 619 /** 620 * Constructs an instance. 621 * 622 * @param bytes {@code non-null;} original classfile's bytes 623 * @param offset offset into {@link #bytes} for the start of the 624 * data 625 * @param size number of elements in the list (not number of bytes) 626 * @param pool {@code non-null;} the constant pool to use 627 * @param observer {@code null-ok;} parse observer to use, if any 628 */ DcfTypeList(ByteArray bytes, int offset, int size, StdConstantPool pool, ParseObserver observer)629 public DcfTypeList(ByteArray bytes, int offset, int size, 630 StdConstantPool pool, ParseObserver observer) { 631 if (size < 0) { 632 throw new IllegalArgumentException("size < 0"); 633 } 634 635 bytes = bytes.slice(offset, offset + size * 2); 636 this.bytes = bytes; 637 this.size = size; 638 this.pool = pool; 639 640 for (int i = 0; i < size; i++) { 641 offset = i * 2; 642 int idx = bytes.getUnsignedShort(offset); 643 CstType type; 644 try { 645 type = (CstType) pool.get(idx); 646 } catch (ClassCastException ex) { 647 // Translate the exception. 648 throw new RuntimeException("bogus class cpi", ex); 649 } 650 if (observer != null) { 651 observer.parsed(bytes, offset, 2, " " + type); 652 } 653 } 654 } 655 656 /** {@inheritDoc} */ 657 @Override isMutable()658 public boolean isMutable() { 659 return false; 660 } 661 662 /** {@inheritDoc} */ 663 @Override size()664 public int size() { 665 return size; 666 } 667 668 /** {@inheritDoc} */ 669 @Override getWordCount()670 public int getWordCount() { 671 // It is the same as size because all elements are classes. 672 return size; 673 } 674 675 /** {@inheritDoc} */ 676 @Override getType(int n)677 public Type getType(int n) { 678 int idx = bytes.getUnsignedShort(n * 2); 679 return ((CstType) pool.get(idx)).getClassType(); 680 } 681 682 /** {@inheritDoc} */ 683 @Override withAddedType(Type type)684 public TypeList withAddedType(Type type) { 685 throw new UnsupportedOperationException("unsupported"); 686 } 687 } 688 } 689