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