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