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