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