| /* |
| * 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.cst; |
| |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Class; |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Double; |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Fieldref; |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Float; |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Integer; |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_InterfaceMethodref; |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Long; |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Methodref; |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_NameAndType; |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_String; |
| import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Utf8; |
| import com.android.dx.cf.iface.ParseException; |
| import com.android.dx.cf.iface.ParseObserver; |
| import com.android.dx.rop.cst.Constant; |
| import com.android.dx.rop.cst.CstDouble; |
| 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.CstInterfaceMethodRef; |
| import com.android.dx.rop.cst.CstLong; |
| import com.android.dx.rop.cst.CstMethodRef; |
| import com.android.dx.rop.cst.CstNat; |
| import com.android.dx.rop.cst.CstString; |
| import com.android.dx.rop.cst.CstType; |
| import com.android.dx.rop.cst.StdConstantPool; |
| import com.android.dx.rop.type.Type; |
| import com.android.dx.util.ByteArray; |
| import com.android.dx.util.Hex; |
| import java.util.BitSet; |
| |
| /** |
| * Parser for a constant pool embedded in a class file. |
| */ |
| public final class ConstantPoolParser { |
| /** {@code non-null;} the bytes of the constant pool */ |
| private final ByteArray bytes; |
| |
| /** {@code non-null;} actual parsed constant pool contents */ |
| private final StdConstantPool pool; |
| |
| /** {@code non-null;} byte offsets to each cst */ |
| private final int[] offsets; |
| |
| /** |
| * -1 || >= 10; the end offset of this constant pool in the |
| * {@code byte[]} which it came from or {@code -1} if not |
| * yet parsed |
| */ |
| private int endOffset; |
| |
| /** {@code null-ok;} parse observer, if any */ |
| private ParseObserver observer; |
| |
| /** |
| * Constructs an instance. |
| * |
| * @param bytes {@code non-null;} the bytes of the file |
| */ |
| public ConstantPoolParser(ByteArray bytes) { |
| int size = bytes.getUnsignedShort(8); // constant_pool_count |
| |
| this.bytes = bytes; |
| this.pool = new StdConstantPool(size); |
| this.offsets = new int[size]; |
| this.endOffset = -1; |
| } |
| |
| /** |
| * Sets the parse observer for this instance. |
| * |
| * @param observer {@code null-ok;} the observer |
| */ |
| public void setObserver(ParseObserver observer) { |
| this.observer = observer; |
| } |
| |
| /** |
| * Gets the end offset of this constant pool in the {@code byte[]} |
| * which it came from. |
| * |
| * @return {@code >= 10;} the end offset |
| */ |
| public int getEndOffset() { |
| parseIfNecessary(); |
| return endOffset; |
| } |
| |
| /** |
| * Gets the actual constant pool. |
| * |
| * @return {@code non-null;} the constant pool |
| */ |
| public StdConstantPool getPool() { |
| parseIfNecessary(); |
| return pool; |
| } |
| |
| /** |
| * Runs {@link #parse} if it has not yet been run successfully. |
| */ |
| private void parseIfNecessary() { |
| if (endOffset < 0) { |
| parse(); |
| } |
| } |
| |
| /** |
| * Does the actual parsing. |
| */ |
| private void parse() { |
| determineOffsets(); |
| |
| if (observer != null) { |
| observer.parsed(bytes, 8, 2, |
| "constant_pool_count: " + Hex.u2(offsets.length)); |
| observer.parsed(bytes, 10, 0, "\nconstant_pool:"); |
| observer.changeIndent(1); |
| } |
| |
| /* |
| * Track the constant value's original string type. True if constants[i] was |
| * a CONSTANT_Utf8, false for any other type including CONSTANT_string. |
| */ |
| BitSet wasUtf8 = new BitSet(offsets.length); |
| |
| for (int i = 1; i < offsets.length; i++) { |
| int offset = offsets[i]; |
| if ((offset != 0) && (pool.getOrNull(i) == null)) { |
| parse0(i, wasUtf8); |
| } |
| } |
| |
| if (observer != null) { |
| for (int i = 1; i < offsets.length; i++) { |
| Constant cst = pool.getOrNull(i); |
| if (cst == null) { |
| continue; |
| } |
| int offset = offsets[i]; |
| int nextOffset = endOffset; |
| for (int j = i + 1; j < offsets.length; j++) { |
| int off = offsets[j]; |
| if (off != 0) { |
| nextOffset = off; |
| break; |
| } |
| } |
| String human = wasUtf8.get(i) |
| ? Hex.u2(i) + ": utf8{\"" + cst.toHuman() + "\"}" |
| : Hex.u2(i) + ": " + cst.toString(); |
| observer.parsed(bytes, offset, nextOffset - offset, human); |
| } |
| |
| observer.changeIndent(-1); |
| observer.parsed(bytes, endOffset, 0, "end constant_pool"); |
| } |
| } |
| |
| /** |
| * Populates {@link #offsets} and also completely parse utf8 constants. |
| */ |
| private void determineOffsets() { |
| int at = 10; // offset from the start of the file to the first cst |
| int lastCategory; |
| |
| for (int i = 1; i < offsets.length; i += lastCategory) { |
| offsets[i] = at; |
| int tag = bytes.getUnsignedByte(at); |
| switch (tag) { |
| case CONSTANT_Integer: |
| case CONSTANT_Float: |
| case CONSTANT_Fieldref: |
| case CONSTANT_Methodref: |
| case CONSTANT_InterfaceMethodref: |
| case CONSTANT_NameAndType: { |
| lastCategory = 1; |
| at += 5; |
| break; |
| } |
| case CONSTANT_Long: |
| case CONSTANT_Double: { |
| lastCategory = 2; |
| at += 9; |
| break; |
| } |
| case CONSTANT_Class: |
| case CONSTANT_String: { |
| lastCategory = 1; |
| at += 3; |
| break; |
| } |
| case CONSTANT_Utf8: { |
| lastCategory = 1; |
| at += bytes.getUnsignedShort(at + 1) + 3; |
| break; |
| } |
| default: { |
| ParseException ex = |
| new ParseException("unknown tag byte: " + Hex.u1(tag)); |
| ex.addContext("...while preparsing cst " + Hex.u2(i) + |
| " at offset " + Hex.u4(at)); |
| throw ex; |
| } |
| } |
| } |
| |
| endOffset = at; |
| } |
| |
| /** |
| * Parses the constant for the given index if it hasn't already been |
| * parsed, also storing it in the constant pool. This will also |
| * have the side effect of parsing any entries the indicated one |
| * depends on. |
| * |
| * @param idx which constant |
| * @return {@code non-null;} the parsed constant |
| */ |
| private Constant parse0(int idx, BitSet wasUtf8) { |
| Constant cst = pool.getOrNull(idx); |
| if (cst != null) { |
| return cst; |
| } |
| |
| int at = offsets[idx]; |
| |
| try { |
| int tag = bytes.getUnsignedByte(at); |
| switch (tag) { |
| case CONSTANT_Utf8: { |
| cst = parseUtf8(at); |
| wasUtf8.set(idx); |
| break; |
| } |
| case CONSTANT_Integer: { |
| int value = bytes.getInt(at + 1); |
| cst = CstInteger.make(value); |
| break; |
| } |
| case CONSTANT_Float: { |
| int bits = bytes.getInt(at + 1); |
| cst = CstFloat.make(bits); |
| break; |
| } |
| case CONSTANT_Long: { |
| long value = bytes.getLong(at + 1); |
| cst = CstLong.make(value); |
| break; |
| } |
| case CONSTANT_Double: { |
| long bits = bytes.getLong(at + 1); |
| cst = CstDouble.make(bits); |
| break; |
| } |
| case CONSTANT_Class: { |
| int nameIndex = bytes.getUnsignedShort(at + 1); |
| CstString name = (CstString) parse0(nameIndex, wasUtf8); |
| cst = new CstType(Type.internClassName(name.getString())); |
| break; |
| } |
| case CONSTANT_String: { |
| int stringIndex = bytes.getUnsignedShort(at + 1); |
| cst = parse0(stringIndex, wasUtf8); |
| break; |
| } |
| case CONSTANT_Fieldref: { |
| int classIndex = bytes.getUnsignedShort(at + 1); |
| CstType type = (CstType) parse0(classIndex, wasUtf8); |
| int natIndex = bytes.getUnsignedShort(at + 3); |
| CstNat nat = (CstNat) parse0(natIndex, wasUtf8); |
| cst = new CstFieldRef(type, nat); |
| break; |
| } |
| case CONSTANT_Methodref: { |
| int classIndex = bytes.getUnsignedShort(at + 1); |
| CstType type = (CstType) parse0(classIndex, wasUtf8); |
| int natIndex = bytes.getUnsignedShort(at + 3); |
| CstNat nat = (CstNat) parse0(natIndex, wasUtf8); |
| cst = new CstMethodRef(type, nat); |
| break; |
| } |
| case CONSTANT_InterfaceMethodref: { |
| int classIndex = bytes.getUnsignedShort(at + 1); |
| CstType type = (CstType) parse0(classIndex, wasUtf8); |
| int natIndex = bytes.getUnsignedShort(at + 3); |
| CstNat nat = (CstNat) parse0(natIndex, wasUtf8); |
| cst = new CstInterfaceMethodRef(type, nat); |
| break; |
| } |
| case CONSTANT_NameAndType: { |
| int nameIndex = bytes.getUnsignedShort(at + 1); |
| CstString name = (CstString) parse0(nameIndex, wasUtf8); |
| int descriptorIndex = bytes.getUnsignedShort(at + 3); |
| CstString descriptor = (CstString) parse0(descriptorIndex, wasUtf8); |
| cst = new CstNat(name, descriptor); |
| break; |
| } |
| } |
| } catch (ParseException ex) { |
| ex.addContext("...while parsing cst " + Hex.u2(idx) + |
| " at offset " + Hex.u4(at)); |
| throw ex; |
| } catch (RuntimeException ex) { |
| ParseException pe = new ParseException(ex); |
| pe.addContext("...while parsing cst " + Hex.u2(idx) + |
| " at offset " + Hex.u4(at)); |
| throw pe; |
| } |
| |
| pool.set(idx, cst); |
| return cst; |
| } |
| |
| /** |
| * Parses a utf8 constant. |
| * |
| * @param at offset to the start of the constant (where the tag byte is) |
| * @return {@code non-null;} the parsed value |
| */ |
| private CstString parseUtf8(int at) { |
| int length = bytes.getUnsignedShort(at + 1); |
| |
| at += 3; // Skip to the data. |
| |
| ByteArray ubytes = bytes.slice(at, at + length); |
| |
| try { |
| return new CstString(ubytes); |
| } catch (IllegalArgumentException ex) { |
| // Translate the exception |
| throw new ParseException(ex); |
| } |
| } |
| } |