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