/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.dx.cf.direct; import com.android.dx.cf.iface.ParseException; import com.android.dx.cf.iface.ParseObserver; import com.android.dx.rop.annotation.Annotation; import com.android.dx.rop.annotation.AnnotationVisibility; import com.android.dx.rop.annotation.Annotations; import com.android.dx.rop.annotation.AnnotationsList; import com.android.dx.rop.annotation.NameValuePair; import com.android.dx.rop.cst.Constant; import com.android.dx.rop.cst.ConstantPool; import com.android.dx.rop.cst.CstAnnotation; import com.android.dx.rop.cst.CstArray; import com.android.dx.rop.cst.CstBoolean; import com.android.dx.rop.cst.CstByte; import com.android.dx.rop.cst.CstChar; import com.android.dx.rop.cst.CstDouble; import com.android.dx.rop.cst.CstEnumRef; import com.android.dx.rop.cst.CstFieldRef; import com.android.dx.rop.cst.CstFloat; import com.android.dx.rop.cst.CstInteger; import com.android.dx.rop.cst.CstLong; import com.android.dx.rop.cst.CstNat; import com.android.dx.rop.cst.CstShort; import com.android.dx.rop.cst.CstString; import com.android.dx.rop.cst.CstType; import com.android.dx.rop.cst.CstUtf8; import com.android.dx.rop.type.Type; import com.android.dx.util.ByteArray; import com.android.dx.util.Hex; import java.io.IOException; /** * Parser for annotations. */ public final class AnnotationParser { /** {@code non-null;} class file being parsed */ private final DirectClassFile cf; /** {@code non-null;} constant pool to use */ private final ConstantPool pool; /** {@code non-null;} bytes of the attribute data */ private final ByteArray bytes; /** {@code null-ok;} parse observer, if any */ private final ParseObserver observer; /** {@code non-null;} input stream to parse from */ private final ByteArray.MyDataInputStream input; /** * {@code non-null;} cursor for use when informing the observer of what * was parsed */ private int parseCursor; /** * Constructs an instance. * * @param cf {@code non-null;} class file to parse from * @param offset {@code >= 0;} offset into the class file data to parse at * @param length {@code >= 0;} number of bytes left in the attribute data * @param observer {@code null-ok;} parse observer to notify, if any */ public AnnotationParser(DirectClassFile cf, int offset, int length, ParseObserver observer) { if (cf == null) { throw new NullPointerException("cf == null"); } this.cf = cf; this.pool = cf.getConstantPool(); this.observer = observer; this.bytes = cf.getBytes().slice(offset, offset + length); this.input = bytes.makeDataInputStream(); this.parseCursor = 0; } /** * Parses an annotation value ({@code element_value}) attribute. * * @return {@code non-null;} the parsed constant value */ public Constant parseValueAttribute() { Constant result; try { result = parseValue(); if (input.available() != 0) { throw new ParseException("extra data in attribute"); } } catch (IOException ex) { // ByteArray.MyDataInputStream should never throw. throw new RuntimeException("shouldn't happen", ex); } return result; } /** * Parses a parameter annotation attribute. * * @param visibility {@code non-null;} visibility of the parsed annotations * @return {@code non-null;} the parsed list of lists of annotations */ public AnnotationsList parseParameterAttribute( AnnotationVisibility visibility) { AnnotationsList result; try { result = parseAnnotationsList(visibility); if (input.available() != 0) { throw new ParseException("extra data in attribute"); } } catch (IOException ex) { // ByteArray.MyDataInputStream should never throw. throw new RuntimeException("shouldn't happen", ex); } return result; } /** * Parses an annotation attribute, per se. * * @param visibility {@code non-null;} visibility of the parsed annotations * @return {@code non-null;} the list of annotations read from the attribute * data */ public Annotations parseAnnotationAttribute( AnnotationVisibility visibility) { Annotations result; try { result = parseAnnotations(visibility); if (input.available() != 0) { throw new ParseException("extra data in attribute"); } } catch (IOException ex) { // ByteArray.MyDataInputStream should never throw. throw new RuntimeException("shouldn't happen", ex); } return result; } /** * Parses a list of annotation lists. * * @param visibility {@code non-null;} visibility of the parsed annotations * @return {@code non-null;} the list of annotation lists read from the attribute * data */ private AnnotationsList parseAnnotationsList( AnnotationVisibility visibility) throws IOException { int count = input.readUnsignedByte(); if (observer != null) { parsed(1, "num_parameters: " + Hex.u1(count)); } AnnotationsList outerList = new AnnotationsList(count); for (int i = 0; i < count; i++) { if (observer != null) { parsed(0, "parameter_annotations[" + i + "]:"); changeIndent(1); } Annotations annotations = parseAnnotations(visibility); outerList.set(i, annotations); if (observer != null) { observer.changeIndent(-1); } } outerList.setImmutable(); return outerList; } /** * Parses an annotation list. * * @param visibility {@code non-null;} visibility of the parsed annotations * @return {@code non-null;} the list of annotations read from the attribute * data */ private Annotations parseAnnotations(AnnotationVisibility visibility) throws IOException { int count = input.readUnsignedShort(); if (observer != null) { parsed(2, "num_annotations: " + Hex.u2(count)); } Annotations annotations = new Annotations(); for (int i = 0; i < count; i++) { if (observer != null) { parsed(0, "annotations[" + i + "]:"); changeIndent(1); } Annotation annotation = parseAnnotation(visibility); annotations.add(annotation); if (observer != null) { observer.changeIndent(-1); } } annotations.setImmutable(); return annotations; } /** * Parses a single annotation. * * @param visibility {@code non-null;} visibility of the parsed annotation * @return {@code non-null;} the parsed annotation */ private Annotation parseAnnotation(AnnotationVisibility visibility) throws IOException { requireLength(4); int typeIndex = input.readUnsignedShort(); int numElements = input.readUnsignedShort(); CstUtf8 typeUtf8 = (CstUtf8) pool.get(typeIndex); CstType type = new CstType(Type.intern(typeUtf8.getString())); if (observer != null) { parsed(2, "type: " + type.toHuman()); parsed(2, "num_elements: " + numElements); } Annotation annotation = new Annotation(type, visibility); for (int i = 0; i < numElements; i++) { if (observer != null) { parsed(0, "elements[" + i + "]:"); changeIndent(1); } NameValuePair element = parseElement(); annotation.add(element); if (observer != null) { changeIndent(-1); } } annotation.setImmutable(); return annotation; } /** * Parses a {@link NameValuePair}. * * @return {@code non-null;} the parsed element */ private NameValuePair parseElement() throws IOException { requireLength(5); int elementNameIndex = input.readUnsignedShort(); CstUtf8 elementName = (CstUtf8) pool.get(elementNameIndex); if (observer != null) { parsed(2, "element_name: " + elementName.toHuman()); parsed(0, "value: "); changeIndent(1); } Constant value = parseValue(); if (observer != null) { changeIndent(-1); } return new NameValuePair(elementName, value); } /** * Parses an annotation value. * * @return {@code non-null;} the parsed value */ private Constant parseValue() throws IOException { int tag = input.readUnsignedByte(); if (observer != null) { CstUtf8 humanTag = new CstUtf8(Character.toString((char) tag)); parsed(1, "tag: " + humanTag.toQuoted()); } switch (tag) { case 'B': { CstInteger value = (CstInteger) parseConstant(); return CstByte.make(value.getValue()); } case 'C': { CstInteger value = (CstInteger) parseConstant(); int intValue = value.getValue(); return CstChar.make(value.getValue()); } case 'D': { CstDouble value = (CstDouble) parseConstant(); return value; } case 'F': { CstFloat value = (CstFloat) parseConstant(); return value; } case 'I': { CstInteger value = (CstInteger) parseConstant(); return value; } case 'J': { CstLong value = (CstLong) parseConstant(); return value; } case 'S': { CstInteger value = (CstInteger) parseConstant(); return CstShort.make(value.getValue()); } case 'Z': { CstInteger value = (CstInteger) parseConstant(); return CstBoolean.make(value.getValue()); } case 'c': { int classInfoIndex = input.readUnsignedShort(); CstUtf8 value = (CstUtf8) pool.get(classInfoIndex); Type type = Type.internReturnType(value.getString()); if (observer != null) { parsed(2, "class_info: " + type.toHuman()); } return new CstType(type); } case 's': { CstString value = new CstString((CstUtf8) parseConstant()); return value; } case 'e': { requireLength(4); int typeNameIndex = input.readUnsignedShort(); int constNameIndex = input.readUnsignedShort(); CstUtf8 typeName = (CstUtf8) pool.get(typeNameIndex); CstUtf8 constName = (CstUtf8) pool.get(constNameIndex); if (observer != null) { parsed(2, "type_name: " + typeName.toHuman()); parsed(2, "const_name: " + constName.toHuman()); } return new CstEnumRef(new CstNat(constName, typeName)); } case '@': { Annotation annotation = parseAnnotation(AnnotationVisibility.EMBEDDED); return new CstAnnotation(annotation); } case '[': { requireLength(2); int numValues = input.readUnsignedShort(); CstArray.List list = new CstArray.List(numValues); if (observer != null) { parsed(2, "num_values: " + numValues); changeIndent(1); } for (int i = 0; i < numValues; i++) { if (observer != null) { changeIndent(-1); parsed(0, "element_value[" + i + "]:"); changeIndent(1); } list.set(i, parseValue()); } if (observer != null) { changeIndent(-1); } list.setImmutable(); return new CstArray(list); } default: { throw new ParseException("unknown annotation tag: " + Hex.u1(tag)); } } } /** * Helper for {@link #parseValue}, which parses a constant reference * and returns the referred-to constant value. * * @return {@code non-null;} the parsed value */ private Constant parseConstant() throws IOException { int constValueIndex = input.readUnsignedShort(); Constant value = (Constant) pool.get(constValueIndex); if (observer != null) { String human = (value instanceof CstUtf8) ? ((CstUtf8) value).toQuoted() : value.toHuman(); parsed(2, "constant_value: " + human); } return value; } /** * Helper which will throw an exception if the given number of bytes * is not available to be read. * * @param requiredLength the number of required bytes */ private void requireLength(int requiredLength) throws IOException { if (input.available() < requiredLength) { throw new ParseException("truncated annotation attribute"); } } /** * Helper which indicates that some bytes were just parsed. This should * only be used (for efficiency sake) if the parse is known to be * observed. * * @param length {@code >= 0;} number of bytes parsed * @param message {@code non-null;} associated message */ private void parsed(int length, String message) { observer.parsed(bytes, parseCursor, length, message); parseCursor += length; } /** * Convenience wrapper that simply calls through to * {@code observer.changeIndent()}. * * @param indent the amount to change the indent by */ private void changeIndent(int indent) { observer.changeIndent(indent); } }