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