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.iface.ParseException; 20 import com.android.dx.cf.iface.ParseObserver; 21 import com.android.dx.rop.annotation.Annotation; 22 import com.android.dx.rop.annotation.AnnotationVisibility; 23 import com.android.dx.rop.annotation.Annotations; 24 import com.android.dx.rop.annotation.AnnotationsList; 25 import com.android.dx.rop.annotation.NameValuePair; 26 import com.android.dx.rop.cst.Constant; 27 import com.android.dx.rop.cst.ConstantPool; 28 import com.android.dx.rop.cst.CstAnnotation; 29 import com.android.dx.rop.cst.CstArray; 30 import com.android.dx.rop.cst.CstBoolean; 31 import com.android.dx.rop.cst.CstByte; 32 import com.android.dx.rop.cst.CstChar; 33 import com.android.dx.rop.cst.CstDouble; 34 import com.android.dx.rop.cst.CstEnumRef; 35 import com.android.dx.rop.cst.CstFloat; 36 import com.android.dx.rop.cst.CstInteger; 37 import com.android.dx.rop.cst.CstLong; 38 import com.android.dx.rop.cst.CstNat; 39 import com.android.dx.rop.cst.CstShort; 40 import com.android.dx.rop.cst.CstString; 41 import com.android.dx.rop.cst.CstType; 42 import com.android.dx.rop.type.Type; 43 import com.android.dx.util.ByteArray; 44 import com.android.dx.util.Hex; 45 import java.io.IOException; 46 47 /** 48 * Parser for annotations. 49 */ 50 public final class AnnotationParser { 51 /** {@code non-null;} class file being parsed */ 52 private final DirectClassFile cf; 53 54 /** {@code non-null;} constant pool to use */ 55 private final ConstantPool pool; 56 57 /** {@code non-null;} bytes of the attribute data */ 58 private final ByteArray bytes; 59 60 /** {@code null-ok;} parse observer, if any */ 61 private final ParseObserver observer; 62 63 /** {@code non-null;} input stream to parse from */ 64 private final ByteArray.MyDataInputStream input; 65 66 /** 67 * {@code non-null;} cursor for use when informing the observer of what 68 * was parsed 69 */ 70 private int parseCursor; 71 72 /** 73 * Constructs an instance. 74 * 75 * @param cf {@code non-null;} class file to parse from 76 * @param offset {@code >= 0;} offset into the class file data to parse at 77 * @param length {@code >= 0;} number of bytes left in the attribute data 78 * @param observer {@code null-ok;} parse observer to notify, if any 79 */ 80 public AnnotationParser(DirectClassFile cf, int offset, int length, 81 ParseObserver observer) { 82 if (cf == null) { 83 throw new NullPointerException("cf == null"); 84 } 85 86 this.cf = cf; 87 this.pool = cf.getConstantPool(); 88 this.observer = observer; 89 this.bytes = cf.getBytes().slice(offset, offset + length); 90 this.input = bytes.makeDataInputStream(); 91 this.parseCursor = 0; 92 } 93 94 /** 95 * Parses an annotation value ({@code element_value}) attribute. 96 * 97 * @return {@code non-null;} the parsed constant value 98 */ 99 public Constant parseValueAttribute() { 100 Constant result; 101 102 try { 103 result = parseValue(); 104 105 if (input.available() != 0) { 106 throw new ParseException("extra data in attribute"); 107 } 108 } catch (IOException ex) { 109 // ByteArray.MyDataInputStream should never throw. 110 throw new RuntimeException("shouldn't happen", ex); 111 } 112 113 return result; 114 } 115 116 /** 117 * Parses a parameter annotation attribute. 118 * 119 * @param visibility {@code non-null;} visibility of the parsed annotations 120 * @return {@code non-null;} the parsed list of lists of annotations 121 */ 122 public AnnotationsList parseParameterAttribute( 123 AnnotationVisibility visibility) { 124 AnnotationsList result; 125 126 try { 127 result = parseAnnotationsList(visibility); 128 129 if (input.available() != 0) { 130 throw new ParseException("extra data in attribute"); 131 } 132 } catch (IOException ex) { 133 // ByteArray.MyDataInputStream should never throw. 134 throw new RuntimeException("shouldn't happen", ex); 135 } 136 137 return result; 138 } 139 140 /** 141 * Parses an annotation attribute, per se. 142 * 143 * @param visibility {@code non-null;} visibility of the parsed annotations 144 * @return {@code non-null;} the list of annotations read from the attribute 145 * data 146 */ 147 public Annotations parseAnnotationAttribute( 148 AnnotationVisibility visibility) { 149 Annotations result; 150 151 try { 152 result = parseAnnotations(visibility); 153 154 if (input.available() != 0) { 155 throw new ParseException("extra data in attribute"); 156 } 157 } catch (IOException ex) { 158 // ByteArray.MyDataInputStream should never throw. 159 throw new RuntimeException("shouldn't happen", ex); 160 } 161 162 return result; 163 } 164 165 /** 166 * Parses a list of annotation lists. 167 * 168 * @param visibility {@code non-null;} visibility of the parsed annotations 169 * @return {@code non-null;} the list of annotation lists read from the attribute 170 * data 171 */ 172 private AnnotationsList parseAnnotationsList( 173 AnnotationVisibility visibility) throws IOException { 174 int count = input.readUnsignedByte(); 175 176 if (observer != null) { 177 parsed(1, "num_parameters: " + Hex.u1(count)); 178 } 179 180 AnnotationsList outerList = new AnnotationsList(count); 181 182 for (int i = 0; i < count; i++) { 183 if (observer != null) { 184 parsed(0, "parameter_annotations[" + i + "]:"); 185 changeIndent(1); 186 } 187 188 Annotations annotations = parseAnnotations(visibility); 189 outerList.set(i, annotations); 190 191 if (observer != null) { 192 observer.changeIndent(-1); 193 } 194 } 195 196 outerList.setImmutable(); 197 return outerList; 198 } 199 200 /** 201 * Parses an annotation list. 202 * 203 * @param visibility {@code non-null;} visibility of the parsed annotations 204 * @return {@code non-null;} the list of annotations read from the attribute 205 * data 206 */ 207 private Annotations parseAnnotations(AnnotationVisibility visibility) 208 throws IOException { 209 int count = input.readUnsignedShort(); 210 211 if (observer != null) { 212 parsed(2, "num_annotations: " + Hex.u2(count)); 213 } 214 215 Annotations annotations = new Annotations(); 216 217 for (int i = 0; i < count; i++) { 218 if (observer != null) { 219 parsed(0, "annotations[" + i + "]:"); 220 changeIndent(1); 221 } 222 223 Annotation annotation = parseAnnotation(visibility); 224 annotations.add(annotation); 225 226 if (observer != null) { 227 observer.changeIndent(-1); 228 } 229 } 230 231 annotations.setImmutable(); 232 return annotations; 233 } 234 235 /** 236 * Parses a single annotation. 237 * 238 * @param visibility {@code non-null;} visibility of the parsed annotation 239 * @return {@code non-null;} the parsed annotation 240 */ 241 private Annotation parseAnnotation(AnnotationVisibility visibility) 242 throws IOException { 243 requireLength(4); 244 245 int typeIndex = input.readUnsignedShort(); 246 int numElements = input.readUnsignedShort(); 247 CstString typeString = (CstString) pool.get(typeIndex); 248 CstType type = new CstType(Type.intern(typeString.getString())); 249 250 if (observer != null) { 251 parsed(2, "type: " + type.toHuman()); 252 parsed(2, "num_elements: " + numElements); 253 } 254 255 Annotation annotation = new Annotation(type, visibility); 256 257 for (int i = 0; i < numElements; i++) { 258 if (observer != null) { 259 parsed(0, "elements[" + i + "]:"); 260 changeIndent(1); 261 } 262 263 NameValuePair element = parseElement(); 264 annotation.add(element); 265 266 if (observer != null) { 267 changeIndent(-1); 268 } 269 } 270 271 annotation.setImmutable(); 272 return annotation; 273 } 274 275 /** 276 * Parses a {@link NameValuePair}. 277 * 278 * @return {@code non-null;} the parsed element 279 */ 280 private NameValuePair parseElement() throws IOException { 281 requireLength(5); 282 283 int elementNameIndex = input.readUnsignedShort(); 284 CstString elementName = (CstString) pool.get(elementNameIndex); 285 286 if (observer != null) { 287 parsed(2, "element_name: " + elementName.toHuman()); 288 parsed(0, "value: "); 289 changeIndent(1); 290 } 291 292 Constant value = parseValue(); 293 294 if (observer != null) { 295 changeIndent(-1); 296 } 297 298 return new NameValuePair(elementName, value); 299 } 300 301 /** 302 * Parses an annotation value. 303 * 304 * @return {@code non-null;} the parsed value 305 */ 306 private Constant parseValue() throws IOException { 307 int tag = input.readUnsignedByte(); 308 309 if (observer != null) { 310 CstString humanTag = new CstString(Character.toString((char) tag)); 311 parsed(1, "tag: " + humanTag.toQuoted()); 312 } 313 314 switch (tag) { 315 case 'B': { 316 CstInteger value = (CstInteger) parseConstant(); 317 return CstByte.make(value.getValue()); 318 } 319 case 'C': { 320 CstInteger value = (CstInteger) parseConstant(); 321 int intValue = value.getValue(); 322 return CstChar.make(value.getValue()); 323 } 324 case 'D': { 325 CstDouble value = (CstDouble) parseConstant(); 326 return value; 327 } 328 case 'F': { 329 CstFloat value = (CstFloat) parseConstant(); 330 return value; 331 } 332 case 'I': { 333 CstInteger value = (CstInteger) parseConstant(); 334 return value; 335 } 336 case 'J': { 337 CstLong value = (CstLong) parseConstant(); 338 return value; 339 } 340 case 'S': { 341 CstInteger value = (CstInteger) parseConstant(); 342 return CstShort.make(value.getValue()); 343 } 344 case 'Z': { 345 CstInteger value = (CstInteger) parseConstant(); 346 return CstBoolean.make(value.getValue()); 347 } 348 case 'c': { 349 int classInfoIndex = input.readUnsignedShort(); 350 CstString value = (CstString) pool.get(classInfoIndex); 351 Type type = Type.internReturnType(value.getString()); 352 353 if (observer != null) { 354 parsed(2, "class_info: " + type.toHuman()); 355 } 356 357 return new CstType(type); 358 } 359 case 's': { 360 return parseConstant(); 361 } 362 case 'e': { 363 requireLength(4); 364 365 int typeNameIndex = input.readUnsignedShort(); 366 int constNameIndex = input.readUnsignedShort(); 367 CstString typeName = (CstString) pool.get(typeNameIndex); 368 CstString constName = (CstString) pool.get(constNameIndex); 369 370 if (observer != null) { 371 parsed(2, "type_name: " + typeName.toHuman()); 372 parsed(2, "const_name: " + constName.toHuman()); 373 } 374 375 return new CstEnumRef(new CstNat(constName, typeName)); 376 } 377 case '@': { 378 Annotation annotation = 379 parseAnnotation(AnnotationVisibility.EMBEDDED); 380 return new CstAnnotation(annotation); 381 } 382 case '[': { 383 requireLength(2); 384 385 int numValues = input.readUnsignedShort(); 386 CstArray.List list = new CstArray.List(numValues); 387 388 if (observer != null) { 389 parsed(2, "num_values: " + numValues); 390 changeIndent(1); 391 } 392 393 for (int i = 0; i < numValues; i++) { 394 if (observer != null) { 395 changeIndent(-1); 396 parsed(0, "element_value[" + i + "]:"); 397 changeIndent(1); 398 } 399 list.set(i, parseValue()); 400 } 401 402 if (observer != null) { 403 changeIndent(-1); 404 } 405 406 list.setImmutable(); 407 return new CstArray(list); 408 } 409 default: { 410 throw new ParseException("unknown annotation tag: " + 411 Hex.u1(tag)); 412 } 413 } 414 } 415 416 /** 417 * Helper for {@link #parseValue}, which parses a constant reference 418 * and returns the referred-to constant value. 419 * 420 * @return {@code non-null;} the parsed value 421 */ 422 private Constant parseConstant() throws IOException { 423 int constValueIndex = input.readUnsignedShort(); 424 Constant value = (Constant) pool.get(constValueIndex); 425 426 if (observer != null) { 427 String human = (value instanceof CstString) 428 ? ((CstString) value).toQuoted() 429 : value.toHuman(); 430 parsed(2, "constant_value: " + human); 431 } 432 433 return value; 434 } 435 436 /** 437 * Helper which will throw an exception if the given number of bytes 438 * is not available to be read. 439 * 440 * @param requiredLength the number of required bytes 441 */ 442 private void requireLength(int requiredLength) throws IOException { 443 if (input.available() < requiredLength) { 444 throw new ParseException("truncated annotation attribute"); 445 } 446 } 447 448 /** 449 * Helper which indicates that some bytes were just parsed. This should 450 * only be used (for efficiency sake) if the parse is known to be 451 * observed. 452 * 453 * @param length {@code >= 0;} number of bytes parsed 454 * @param message {@code non-null;} associated message 455 */ 456 private void parsed(int length, String message) { 457 observer.parsed(bytes, parseCursor, length, message); 458 parseCursor += length; 459 } 460 461 /** 462 * Convenience wrapper that simply calls through to 463 * {@code observer.changeIndent()}. 464 * 465 * @param indent the amount to change the indent by 466 */ 467 private void changeIndent(int indent) { 468 observer.changeIndent(indent); 469 } 470 } 471