1 /* 2 * Copyright (C) 2008 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.dexgen.dex.file; 18 19 import com.android.dexgen.rop.cst.Constant; 20 import com.android.dexgen.rop.cst.CstArray; 21 import com.android.dexgen.rop.cst.CstLiteralBits; 22 import com.android.dexgen.rop.cst.CstType; 23 import com.android.dexgen.rop.cst.Zeroes; 24 import com.android.dexgen.util.AnnotatedOutput; 25 import com.android.dexgen.util.ByteArrayAnnotatedOutput; 26 import com.android.dexgen.util.Hex; 27 import com.android.dexgen.util.Writers; 28 29 import java.io.PrintWriter; 30 import java.io.Writer; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.Collections; 34 import java.util.List; 35 import java.util.HashMap; 36 37 /** 38 * Representation of all the parts of a Dalvik class that are generally 39 * "inflated" into an in-memory representation at runtime. Instances of 40 * this class are represented in a compact streamable form in a 41 * {@code dex} file, as opposed to a random-access form. 42 */ 43 public final class ClassDataItem extends OffsettedItem { 44 /** {@code non-null;} what class this data is for, just for listing generation */ 45 private final CstType thisClass; 46 47 /** {@code non-null;} list of static fields */ 48 private final ArrayList<EncodedField> staticFields; 49 50 /** {@code non-null;} list of initial values for static fields */ 51 private final HashMap<EncodedField, Constant> staticValues; 52 53 /** {@code non-null;} list of instance fields */ 54 private final ArrayList<EncodedField> instanceFields; 55 56 /** {@code non-null;} list of direct methods */ 57 private final ArrayList<EncodedMethod> directMethods; 58 59 /** {@code non-null;} list of virtual methods */ 60 private final ArrayList<EncodedMethod> virtualMethods; 61 62 /** {@code null-ok;} static initializer list; set in {@link #addContents} */ 63 private CstArray staticValuesConstant; 64 65 /** 66 * {@code null-ok;} encoded form, ready for writing to a file; set during 67 * {@link #place0} 68 */ 69 private byte[] encodedForm; 70 71 /** 72 * Constructs an instance. Its sets of members are initially 73 * empty. 74 * 75 * @param thisClass {@code non-null;} what class this data is for, just 76 * for listing generation 77 */ ClassDataItem(CstType thisClass)78 public ClassDataItem(CstType thisClass) { 79 super(1, -1); 80 81 if (thisClass == null) { 82 throw new NullPointerException("thisClass == null"); 83 } 84 85 this.thisClass = thisClass; 86 this.staticFields = new ArrayList<EncodedField>(20); 87 this.staticValues = new HashMap<EncodedField, Constant>(40); 88 this.instanceFields = new ArrayList<EncodedField>(20); 89 this.directMethods = new ArrayList<EncodedMethod>(20); 90 this.virtualMethods = new ArrayList<EncodedMethod>(20); 91 this.staticValuesConstant = null; 92 } 93 94 /** {@inheritDoc} */ 95 @Override itemType()96 public ItemType itemType() { 97 return ItemType.TYPE_CLASS_DATA_ITEM; 98 } 99 100 /** {@inheritDoc} */ 101 @Override toHuman()102 public String toHuman() { 103 return toString(); 104 } 105 106 /** 107 * Returns whether this instance is empty. 108 * 109 * @return {@code true} if this instance is empty or 110 * {@code false} if at least one element has been added to it 111 */ isEmpty()112 public boolean isEmpty() { 113 return staticFields.isEmpty() && instanceFields.isEmpty() 114 && directMethods.isEmpty() && virtualMethods.isEmpty(); 115 } 116 117 /** 118 * Adds a static field. 119 * 120 * @param field {@code non-null;} the field to add 121 * @param value {@code null-ok;} initial value for the field, if any 122 */ addStaticField(EncodedField field, Constant value)123 public void addStaticField(EncodedField field, Constant value) { 124 if (field == null) { 125 throw new NullPointerException("field == null"); 126 } 127 128 if (staticValuesConstant != null) { 129 throw new UnsupportedOperationException( 130 "static fields already sorted"); 131 } 132 133 staticFields.add(field); 134 staticValues.put(field, value); 135 } 136 137 /** 138 * Adds an instance field. 139 * 140 * @param field {@code non-null;} the field to add 141 */ addInstanceField(EncodedField field)142 public void addInstanceField(EncodedField field) { 143 if (field == null) { 144 throw new NullPointerException("field == null"); 145 } 146 147 instanceFields.add(field); 148 } 149 150 /** 151 * Adds a direct ({@code static} and/or {@code private}) method. 152 * 153 * @param method {@code non-null;} the method to add 154 */ addDirectMethod(EncodedMethod method)155 public void addDirectMethod(EncodedMethod method) { 156 if (method == null) { 157 throw new NullPointerException("method == null"); 158 } 159 160 directMethods.add(method); 161 } 162 163 /** 164 * Adds a virtual method. 165 * 166 * @param method {@code non-null;} the method to add 167 */ addVirtualMethod(EncodedMethod method)168 public void addVirtualMethod(EncodedMethod method) { 169 if (method == null) { 170 throw new NullPointerException("method == null"); 171 } 172 173 virtualMethods.add(method); 174 } 175 176 /** 177 * Gets all the methods in this class. The returned list is not linked 178 * in any way to the underlying lists contained in this instance, but 179 * the objects contained in the list are shared. 180 * 181 * @return {@code non-null;} list of all methods 182 */ getMethods()183 public ArrayList<EncodedMethod> getMethods() { 184 int sz = directMethods.size() + virtualMethods.size(); 185 ArrayList<EncodedMethod> result = new ArrayList<EncodedMethod>(sz); 186 187 result.addAll(directMethods); 188 result.addAll(virtualMethods); 189 190 return result; 191 } 192 193 194 /** 195 * Prints out the contents of this instance, in a debugging-friendly 196 * way. 197 * 198 * @param out {@code non-null;} where to output to 199 * @param verbose whether to be verbose with the output 200 */ debugPrint(Writer out, boolean verbose)201 public void debugPrint(Writer out, boolean verbose) { 202 PrintWriter pw = Writers.printWriterFor(out); 203 204 int sz = staticFields.size(); 205 for (int i = 0; i < sz; i++) { 206 pw.println(" sfields[" + i + "]: " + staticFields.get(i)); 207 } 208 209 sz = instanceFields.size(); 210 for (int i = 0; i < sz; i++) { 211 pw.println(" ifields[" + i + "]: " + instanceFields.get(i)); 212 } 213 214 sz = directMethods.size(); 215 for (int i = 0; i < sz; i++) { 216 pw.println(" dmeths[" + i + "]:"); 217 directMethods.get(i).debugPrint(pw, verbose); 218 } 219 220 sz = virtualMethods.size(); 221 for (int i = 0; i < sz; i++) { 222 pw.println(" vmeths[" + i + "]:"); 223 virtualMethods.get(i).debugPrint(pw, verbose); 224 } 225 } 226 227 /** {@inheritDoc} */ 228 @Override addContents(DexFile file)229 public void addContents(DexFile file) { 230 if (!staticFields.isEmpty()) { 231 getStaticValuesConstant(); // Force the fields to be sorted. 232 for (EncodedField field : staticFields) { 233 field.addContents(file); 234 } 235 } 236 237 if (!instanceFields.isEmpty()) { 238 Collections.sort(instanceFields); 239 for (EncodedField field : instanceFields) { 240 field.addContents(file); 241 } 242 } 243 244 if (!directMethods.isEmpty()) { 245 Collections.sort(directMethods); 246 for (EncodedMethod method : directMethods) { 247 method.addContents(file); 248 } 249 } 250 251 if (!virtualMethods.isEmpty()) { 252 Collections.sort(virtualMethods); 253 for (EncodedMethod method : virtualMethods) { 254 method.addContents(file); 255 } 256 } 257 } 258 259 /** 260 * Gets a {@link CstArray} corresponding to {@link #staticValues} if 261 * it contains any non-zero non-{@code null} values. 262 * 263 * @return {@code null-ok;} the corresponding constant or {@code null} if 264 * there are no values to encode 265 */ getStaticValuesConstant()266 public CstArray getStaticValuesConstant() { 267 if ((staticValuesConstant == null) && (staticFields.size() != 0)) { 268 staticValuesConstant = makeStaticValuesConstant(); 269 } 270 271 return staticValuesConstant; 272 } 273 274 /** 275 * Gets a {@link CstArray} corresponding to {@link #staticValues} if 276 * it contains any non-zero non-{@code null} values. 277 * 278 * @return {@code null-ok;} the corresponding constant or {@code null} if 279 * there are no values to encode 280 */ makeStaticValuesConstant()281 private CstArray makeStaticValuesConstant() { 282 // First sort the statics into their final order. 283 Collections.sort(staticFields); 284 285 /* 286 * Get the size of staticValues minus any trailing zeros/nulls (both 287 * nulls per se as well as instances of CstKnownNull). 288 */ 289 290 int size = staticFields.size(); 291 while (size > 0) { 292 EncodedField field = staticFields.get(size - 1); 293 Constant cst = staticValues.get(field); 294 if (cst instanceof CstLiteralBits) { 295 // Note: CstKnownNull extends CstLiteralBits. 296 if (((CstLiteralBits) cst).getLongBits() != 0) { 297 break; 298 } 299 } else if (cst != null) { 300 break; 301 } 302 size--; 303 } 304 305 if (size == 0) { 306 return null; 307 } 308 309 // There is something worth encoding, so build up a result. 310 311 CstArray.List list = new CstArray.List(size); 312 for (int i = 0; i < size; i++) { 313 EncodedField field = staticFields.get(i); 314 Constant cst = staticValues.get(field); 315 if (cst == null) { 316 cst = Zeroes.zeroFor(field.getRef().getType()); 317 } 318 list.set(i, cst); 319 } 320 list.setImmutable(); 321 322 return new CstArray(list); 323 } 324 325 /** {@inheritDoc} */ 326 @Override place0(Section addedTo, int offset)327 protected void place0(Section addedTo, int offset) { 328 // Encode the data and note the size. 329 330 ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); 331 332 encodeOutput(addedTo.getFile(), out); 333 encodedForm = out.toByteArray(); 334 setWriteSize(encodedForm.length); 335 } 336 337 /** 338 * Writes out the encoded form of this instance. 339 * 340 * @param file {@code non-null;} file this instance is part of 341 * @param out {@code non-null;} where to write to 342 */ encodeOutput(DexFile file, AnnotatedOutput out)343 private void encodeOutput(DexFile file, AnnotatedOutput out) { 344 boolean annotates = out.annotates(); 345 346 if (annotates) { 347 out.annotate(0, offsetString() + " class data for " + 348 thisClass.toHuman()); 349 } 350 351 encodeSize(file, out, "static_fields", staticFields.size()); 352 encodeSize(file, out, "instance_fields", instanceFields.size()); 353 encodeSize(file, out, "direct_methods", directMethods.size()); 354 encodeSize(file, out, "virtual_methods", virtualMethods.size()); 355 356 encodeList(file, out, "static_fields", staticFields); 357 encodeList(file, out, "instance_fields", instanceFields); 358 encodeList(file, out, "direct_methods", directMethods); 359 encodeList(file, out, "virtual_methods", virtualMethods); 360 361 if (annotates) { 362 out.endAnnotation(); 363 } 364 } 365 366 /** 367 * Helper for {@link #encodeOutput}, which writes out the given 368 * size value, annotating it as well (if annotations are enabled). 369 * 370 * @param file {@code non-null;} file this instance is part of 371 * @param out {@code non-null;} where to write to 372 * @param label {@code non-null;} the label for the purposes of annotation 373 * @param size {@code >= 0;} the size to write 374 */ encodeSize(DexFile file, AnnotatedOutput out, String label, int size)375 private static void encodeSize(DexFile file, AnnotatedOutput out, 376 String label, int size) { 377 if (out.annotates()) { 378 out.annotate(String.format(" %-21s %08x", label + "_size:", 379 size)); 380 } 381 382 out.writeUnsignedLeb128(size); 383 } 384 385 /** 386 * Helper for {@link #encodeOutput}, which writes out the given 387 * list. It also annotates the items (if any and if annotations 388 * are enabled). 389 * 390 * @param file {@code non-null;} file this instance is part of 391 * @param out {@code non-null;} where to write to 392 * @param label {@code non-null;} the label for the purposes of annotation 393 * @param list {@code non-null;} the list in question 394 */ encodeList(DexFile file, AnnotatedOutput out, String label, ArrayList<? extends EncodedMember> list)395 private static void encodeList(DexFile file, AnnotatedOutput out, 396 String label, ArrayList<? extends EncodedMember> list) { 397 int size = list.size(); 398 int lastIndex = 0; 399 400 if (size == 0) { 401 return; 402 } 403 404 if (out.annotates()) { 405 out.annotate(0, " " + label + ":"); 406 } 407 408 for (int i = 0; i < size; i++) { 409 lastIndex = list.get(i).encode(file, out, lastIndex, i); 410 } 411 } 412 413 /** {@inheritDoc} */ 414 @Override writeTo0(DexFile file, AnnotatedOutput out)415 public void writeTo0(DexFile file, AnnotatedOutput out) { 416 boolean annotates = out.annotates(); 417 418 if (annotates) { 419 /* 420 * The output is to be annotated, so redo the work previously 421 * done by place0(), except this time annotations will actually 422 * get emitted. 423 */ 424 encodeOutput(file, out); 425 } else { 426 out.write(encodedForm); 427 } 428 } 429 } 430