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