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.cst; 18 19import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Class; 20import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Double; 21import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Fieldref; 22import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Float; 23import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Integer; 24import static com.android.dx.cf.cst.ConstantTags.CONSTANT_InterfaceMethodref; 25import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Long; 26import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Methodref; 27import static com.android.dx.cf.cst.ConstantTags.CONSTANT_NameAndType; 28import static com.android.dx.cf.cst.ConstantTags.CONSTANT_String; 29import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Utf8; 30import com.android.dx.cf.iface.ParseException; 31import com.android.dx.cf.iface.ParseObserver; 32import com.android.dx.rop.cst.Constant; 33import com.android.dx.rop.cst.CstDouble; 34import com.android.dx.rop.cst.CstFieldRef; 35import com.android.dx.rop.cst.CstFloat; 36import com.android.dx.rop.cst.CstInteger; 37import com.android.dx.rop.cst.CstInterfaceMethodRef; 38import com.android.dx.rop.cst.CstLong; 39import com.android.dx.rop.cst.CstMethodRef; 40import com.android.dx.rop.cst.CstNat; 41import com.android.dx.rop.cst.CstString; 42import com.android.dx.rop.cst.CstType; 43import com.android.dx.rop.cst.StdConstantPool; 44import com.android.dx.rop.type.Type; 45import com.android.dx.util.ByteArray; 46import com.android.dx.util.Hex; 47import java.util.BitSet; 48 49/** 50 * Parser for a constant pool embedded in a class file. 51 */ 52public final class ConstantPoolParser { 53 /** {@code non-null;} the bytes of the constant pool */ 54 private final ByteArray bytes; 55 56 /** {@code non-null;} actual parsed constant pool contents */ 57 private final StdConstantPool pool; 58 59 /** {@code non-null;} byte offsets to each cst */ 60 private final int[] offsets; 61 62 /** 63 * -1 || >= 10; the end offset of this constant pool in the 64 * {@code byte[]} which it came from or {@code -1} if not 65 * yet parsed 66 */ 67 private int endOffset; 68 69 /** {@code null-ok;} parse observer, if any */ 70 private ParseObserver observer; 71 72 /** 73 * Constructs an instance. 74 * 75 * @param bytes {@code non-null;} the bytes of the file 76 */ 77 public ConstantPoolParser(ByteArray bytes) { 78 int size = bytes.getUnsignedShort(8); // constant_pool_count 79 80 this.bytes = bytes; 81 this.pool = new StdConstantPool(size); 82 this.offsets = new int[size]; 83 this.endOffset = -1; 84 } 85 86 /** 87 * Sets the parse observer for this instance. 88 * 89 * @param observer {@code null-ok;} the observer 90 */ 91 public void setObserver(ParseObserver observer) { 92 this.observer = observer; 93 } 94 95 /** 96 * Gets the end offset of this constant pool in the {@code byte[]} 97 * which it came from. 98 * 99 * @return {@code >= 10;} the end offset 100 */ 101 public int getEndOffset() { 102 parseIfNecessary(); 103 return endOffset; 104 } 105 106 /** 107 * Gets the actual constant pool. 108 * 109 * @return {@code non-null;} the constant pool 110 */ 111 public StdConstantPool getPool() { 112 parseIfNecessary(); 113 return pool; 114 } 115 116 /** 117 * Runs {@link #parse} if it has not yet been run successfully. 118 */ 119 private void parseIfNecessary() { 120 if (endOffset < 0) { 121 parse(); 122 } 123 } 124 125 /** 126 * Does the actual parsing. 127 */ 128 private void parse() { 129 determineOffsets(); 130 131 if (observer != null) { 132 observer.parsed(bytes, 8, 2, 133 "constant_pool_count: " + Hex.u2(offsets.length)); 134 observer.parsed(bytes, 10, 0, "\nconstant_pool:"); 135 observer.changeIndent(1); 136 } 137 138 /* 139 * Track the constant value's original string type. True if constants[i] was 140 * a CONSTANT_Utf8, false for any other type including CONSTANT_string. 141 */ 142 BitSet wasUtf8 = new BitSet(offsets.length); 143 144 for (int i = 1; i < offsets.length; i++) { 145 int offset = offsets[i]; 146 if ((offset != 0) && (pool.getOrNull(i) == null)) { 147 parse0(i, wasUtf8); 148 } 149 } 150 151 if (observer != null) { 152 for (int i = 1; i < offsets.length; i++) { 153 Constant cst = pool.getOrNull(i); 154 if (cst == null) { 155 continue; 156 } 157 int offset = offsets[i]; 158 int nextOffset = endOffset; 159 for (int j = i + 1; j < offsets.length; j++) { 160 int off = offsets[j]; 161 if (off != 0) { 162 nextOffset = off; 163 break; 164 } 165 } 166 String human = wasUtf8.get(i) 167 ? Hex.u2(i) + ": utf8{\"" + cst.toHuman() + "\"}" 168 : Hex.u2(i) + ": " + cst.toString(); 169 observer.parsed(bytes, offset, nextOffset - offset, human); 170 } 171 172 observer.changeIndent(-1); 173 observer.parsed(bytes, endOffset, 0, "end constant_pool"); 174 } 175 } 176 177 /** 178 * Populates {@link #offsets} and also completely parse utf8 constants. 179 */ 180 private void determineOffsets() { 181 int at = 10; // offset from the start of the file to the first cst 182 int lastCategory; 183 184 for (int i = 1; i < offsets.length; i += lastCategory) { 185 offsets[i] = at; 186 int tag = bytes.getUnsignedByte(at); 187 switch (tag) { 188 case CONSTANT_Integer: 189 case CONSTANT_Float: 190 case CONSTANT_Fieldref: 191 case CONSTANT_Methodref: 192 case CONSTANT_InterfaceMethodref: 193 case CONSTANT_NameAndType: { 194 lastCategory = 1; 195 at += 5; 196 break; 197 } 198 case CONSTANT_Long: 199 case CONSTANT_Double: { 200 lastCategory = 2; 201 at += 9; 202 break; 203 } 204 case CONSTANT_Class: 205 case CONSTANT_String: { 206 lastCategory = 1; 207 at += 3; 208 break; 209 } 210 case CONSTANT_Utf8: { 211 lastCategory = 1; 212 at += bytes.getUnsignedShort(at + 1) + 3; 213 break; 214 } 215 default: { 216 ParseException ex = 217 new ParseException("unknown tag byte: " + Hex.u1(tag)); 218 ex.addContext("...while preparsing cst " + Hex.u2(i) + 219 " at offset " + Hex.u4(at)); 220 throw ex; 221 } 222 } 223 } 224 225 endOffset = at; 226 } 227 228 /** 229 * Parses the constant for the given index if it hasn't already been 230 * parsed, also storing it in the constant pool. This will also 231 * have the side effect of parsing any entries the indicated one 232 * depends on. 233 * 234 * @param idx which constant 235 * @return {@code non-null;} the parsed constant 236 */ 237 private Constant parse0(int idx, BitSet wasUtf8) { 238 Constant cst = pool.getOrNull(idx); 239 if (cst != null) { 240 return cst; 241 } 242 243 int at = offsets[idx]; 244 245 try { 246 int tag = bytes.getUnsignedByte(at); 247 switch (tag) { 248 case CONSTANT_Utf8: { 249 cst = parseUtf8(at); 250 wasUtf8.set(idx); 251 break; 252 } 253 case CONSTANT_Integer: { 254 int value = bytes.getInt(at + 1); 255 cst = CstInteger.make(value); 256 break; 257 } 258 case CONSTANT_Float: { 259 int bits = bytes.getInt(at + 1); 260 cst = CstFloat.make(bits); 261 break; 262 } 263 case CONSTANT_Long: { 264 long value = bytes.getLong(at + 1); 265 cst = CstLong.make(value); 266 break; 267 } 268 case CONSTANT_Double: { 269 long bits = bytes.getLong(at + 1); 270 cst = CstDouble.make(bits); 271 break; 272 } 273 case CONSTANT_Class: { 274 int nameIndex = bytes.getUnsignedShort(at + 1); 275 CstString name = (CstString) parse0(nameIndex, wasUtf8); 276 cst = new CstType(Type.internClassName(name.getString())); 277 break; 278 } 279 case CONSTANT_String: { 280 int stringIndex = bytes.getUnsignedShort(at + 1); 281 cst = parse0(stringIndex, wasUtf8); 282 break; 283 } 284 case CONSTANT_Fieldref: { 285 int classIndex = bytes.getUnsignedShort(at + 1); 286 CstType type = (CstType) parse0(classIndex, wasUtf8); 287 int natIndex = bytes.getUnsignedShort(at + 3); 288 CstNat nat = (CstNat) parse0(natIndex, wasUtf8); 289 cst = new CstFieldRef(type, nat); 290 break; 291 } 292 case CONSTANT_Methodref: { 293 int classIndex = bytes.getUnsignedShort(at + 1); 294 CstType type = (CstType) parse0(classIndex, wasUtf8); 295 int natIndex = bytes.getUnsignedShort(at + 3); 296 CstNat nat = (CstNat) parse0(natIndex, wasUtf8); 297 cst = new CstMethodRef(type, nat); 298 break; 299 } 300 case CONSTANT_InterfaceMethodref: { 301 int classIndex = bytes.getUnsignedShort(at + 1); 302 CstType type = (CstType) parse0(classIndex, wasUtf8); 303 int natIndex = bytes.getUnsignedShort(at + 3); 304 CstNat nat = (CstNat) parse0(natIndex, wasUtf8); 305 cst = new CstInterfaceMethodRef(type, nat); 306 break; 307 } 308 case CONSTANT_NameAndType: { 309 int nameIndex = bytes.getUnsignedShort(at + 1); 310 CstString name = (CstString) parse0(nameIndex, wasUtf8); 311 int descriptorIndex = bytes.getUnsignedShort(at + 3); 312 CstString descriptor = (CstString) parse0(descriptorIndex, wasUtf8); 313 cst = new CstNat(name, descriptor); 314 break; 315 } 316 } 317 } catch (ParseException ex) { 318 ex.addContext("...while parsing cst " + Hex.u2(idx) + 319 " at offset " + Hex.u4(at)); 320 throw ex; 321 } catch (RuntimeException ex) { 322 ParseException pe = new ParseException(ex); 323 pe.addContext("...while parsing cst " + Hex.u2(idx) + 324 " at offset " + Hex.u4(at)); 325 throw pe; 326 } 327 328 pool.set(idx, cst); 329 return cst; 330 } 331 332 /** 333 * Parses a utf8 constant. 334 * 335 * @param at offset to the start of the constant (where the tag byte is) 336 * @return {@code non-null;} the parsed value 337 */ 338 private CstString parseUtf8(int at) { 339 int length = bytes.getUnsignedShort(at + 1); 340 341 at += 3; // Skip to the data. 342 343 ByteArray ubytes = bytes.slice(at, at + length); 344 345 try { 346 return new CstString(ubytes); 347 } catch (IllegalArgumentException ex) { 348 // Translate the exception 349 throw new ParseException(ex); 350 } 351 } 352} 353