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