ClassDefinition.java revision 3f70d08442fadd8f2af225f86ca2a41334182dec
1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2010 Ben Gruver (JesusFreke)
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29package org.jf.baksmali.Adaptors;
30
31import org.jf.dexlib.Util.Utf8Utils;
32import org.jf.util.CommentingIndentingWriter;
33import org.jf.util.IndentingWriter;
34import org.jf.dexlib.*;
35import org.jf.dexlib.Code.Analysis.ValidationException;
36import org.jf.dexlib.Code.Format.Instruction21c;
37import org.jf.dexlib.Code.Format.Instruction41c;
38import org.jf.dexlib.Code.Instruction;
39import org.jf.dexlib.EncodedValue.EncodedValue;
40import org.jf.dexlib.Util.AccessFlags;
41import org.jf.dexlib.Util.SparseArray;
42
43import javax.annotation.Nullable;
44import java.io.IOException;
45import java.util.List;
46
47public class ClassDefinition {
48    private ClassDefItem classDefItem;
49    @Nullable
50    private ClassDataItem classDataItem;
51
52    private SparseArray<FieldIdItem> fieldsSetInStaticConstructor;
53
54    protected boolean validationErrors;
55
56    public ClassDefinition(ClassDefItem classDefItem) {
57        this.classDefItem = classDefItem;
58        this.classDataItem = classDefItem.getClassData();
59        findFieldsSetInStaticConstructor();
60    }
61
62    public boolean hadValidationErrors() {
63        return validationErrors;
64    }
65
66    private void findFieldsSetInStaticConstructor() {
67        fieldsSetInStaticConstructor = new SparseArray<FieldIdItem>();
68
69        if (classDataItem == null) {
70            return;
71        }
72
73        for (ClassDataItem.EncodedMethod directMethod: classDataItem.getDirectMethods()) {
74            if (directMethod.method.getMethodName().getStringValue().equals("<clinit>") &&
75                    directMethod.codeItem != null) {
76                for (Instruction instruction: directMethod.codeItem.getInstructions()) {
77                    switch (instruction.opcode) {
78                        case SPUT:
79                        case SPUT_BOOLEAN:
80                        case SPUT_BYTE:
81                        case SPUT_CHAR:
82                        case SPUT_OBJECT:
83                        case SPUT_SHORT:
84                        case SPUT_WIDE: {
85                            Instruction21c ins = (Instruction21c)instruction;
86                            FieldIdItem fieldIdItem = (FieldIdItem)ins.getReferencedItem();
87                            fieldsSetInStaticConstructor.put(fieldIdItem.getIndex(), fieldIdItem);
88                            break;
89                        }
90                        case SPUT_JUMBO:
91                        case SPUT_BOOLEAN_JUMBO:
92                        case SPUT_BYTE_JUMBO:
93                        case SPUT_CHAR_JUMBO:
94                        case SPUT_OBJECT_JUMBO:
95                        case SPUT_SHORT_JUMBO:
96                        case SPUT_WIDE_JUMBO: {
97                            Instruction41c ins = (Instruction41c)instruction;
98                            FieldIdItem fieldIdItem = (FieldIdItem)ins.getReferencedItem();
99                            fieldsSetInStaticConstructor.put(fieldIdItem.getIndex(), fieldIdItem);
100                            break;
101                        }
102                    }
103                }
104            }
105        }
106    }
107
108    public void writeTo(IndentingWriter writer) throws IOException {
109        writeClass(writer);
110        writeSuper(writer);
111        writeSourceFile(writer);
112        writeInterfaces(writer);
113        writeAnnotations(writer);
114        writeStaticFields(writer);
115        writeInstanceFields(writer);
116        writeDirectMethods(writer);
117        writeVirtualMethods(writer);
118    }
119
120    private void writeClass(IndentingWriter writer) throws IOException {
121        writer.write(".class ");
122        writeAccessFlags(writer);
123        writer.write(classDefItem.getClassType().getTypeDescriptor());
124        writer.write('\n');
125    }
126
127    private void writeAccessFlags(IndentingWriter writer) throws IOException {
128        for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForClass(classDefItem.getAccessFlags())) {
129            writer.write(accessFlag.toString());
130            writer.write(' ');
131        }
132    }
133
134    private void writeSuper(IndentingWriter writer) throws IOException {
135        TypeIdItem superClass = classDefItem.getSuperclass();
136        if (superClass != null) {
137            writer.write(".super ");
138            writer.write(superClass.getTypeDescriptor());
139            writer.write('\n');
140        }
141    }
142
143    private void writeSourceFile(IndentingWriter writer) throws IOException {
144        StringIdItem sourceFile = classDefItem.getSourceFile();
145        if (sourceFile != null) {
146            writer.write(".source \"");
147            Utf8Utils.writeEscapedString(writer, sourceFile.getStringValue());
148            writer.write("\"\n");
149        }
150    }
151
152    private void writeInterfaces(IndentingWriter writer) throws IOException {
153        TypeListItem interfaceList = classDefItem.getInterfaces();
154        if (interfaceList == null) {
155            return;
156        }
157
158        List<TypeIdItem> interfaces = interfaceList.getTypes();
159        if (interfaces == null || interfaces.size() == 0) {
160            return;
161        }
162
163        writer.write('\n');
164        writer.write("# interfaces\n");
165        for (TypeIdItem typeIdItem: interfaceList.getTypes()) {
166            writer.write(".implements ");
167            writer.write(typeIdItem.getTypeDescriptor());
168            writer.write('\n');
169        }
170    }
171
172    private void writeAnnotations(IndentingWriter writer) throws IOException {
173        AnnotationDirectoryItem annotationDirectory = classDefItem.getAnnotations();
174        if (annotationDirectory == null) {
175            return;
176        }
177
178        AnnotationSetItem annotationSet = annotationDirectory.getClassAnnotations();
179        if (annotationSet == null) {
180            return;
181        }
182
183        writer.write("\n\n");
184        writer.write("# annotations\n");
185        AnnotationFormatter.writeTo(writer, annotationSet);
186    }
187
188    private void writeStaticFields(IndentingWriter writer) throws IOException {
189        if (classDataItem == null) {
190            return;
191        }
192        //if classDataItem is not null, then classDefItem won't be null either
193        assert(classDefItem != null);
194
195        EncodedArrayItem encodedStaticInitializers = classDefItem.getStaticFieldInitializers();
196
197        EncodedValue[] staticInitializers;
198        if (encodedStaticInitializers != null) {
199            staticInitializers = encodedStaticInitializers.getEncodedArray().values;
200        } else {
201            staticInitializers = new EncodedValue[0];
202        }
203
204        List<ClassDataItem.EncodedField> encodedFields = classDataItem.getStaticFields();
205        if (encodedFields.size() == 0) {
206            return;
207        }
208
209        writer.write("\n\n");
210        writer.write("# static fields\n");
211
212        for (int i=0; i<encodedFields.size(); i++) {
213            if (i > 0) {
214                writer.write('\n');
215            }
216
217            ClassDataItem.EncodedField field = encodedFields.get(i);
218            EncodedValue encodedValue = null;
219            if (i < staticInitializers.length) {
220                encodedValue = staticInitializers[i];
221            }
222            AnnotationSetItem fieldAnnotations = null;
223            AnnotationDirectoryItem annotations = classDefItem.getAnnotations();
224            if (annotations != null) {
225                fieldAnnotations = annotations.getFieldAnnotations(field.field);
226            }
227
228            IndentingWriter fieldWriter = writer;
229            // the encoded fields are sorted, so we just have to compare with the previous one to detect duplicates
230            if (i > 0 && field.equals(encodedFields.get(i-1))) {
231                fieldWriter = new CommentingIndentingWriter(writer, "#");
232                fieldWriter.write("Ignoring field with duplicate signature\n");
233                System.err.println(String.format("Warning: class %s has duplicate static field %s, Ignoring.",
234                        classDefItem.getClassType().getTypeDescriptor(), field.field.getShortFieldString()));
235            }
236
237            boolean setInStaticConstructor =
238                    fieldsSetInStaticConstructor.get(field.field.getIndex()) != null;
239
240            FieldDefinition.writeTo(fieldWriter, field, encodedValue, fieldAnnotations, setInStaticConstructor);
241        }
242    }
243
244    private void writeInstanceFields(IndentingWriter writer) throws IOException {
245        if (classDataItem == null) {
246            return;
247        }
248
249        List<ClassDataItem.EncodedField> encodedFields = classDataItem.getInstanceFields();
250        if (encodedFields.size() == 0) {
251            return;
252        }
253
254        writer.write("\n\n");
255        writer.write("# instance fields\n");
256        for (int i=0; i<encodedFields.size(); i++) {
257            ClassDataItem.EncodedField field = encodedFields.get(i);
258
259            if (i > 0) {
260                writer.write('\n');
261            }
262
263            AnnotationSetItem fieldAnnotations = null;
264            AnnotationDirectoryItem annotations = classDefItem.getAnnotations();
265            if (annotations != null) {
266                fieldAnnotations = annotations.getFieldAnnotations(field.field);
267            }
268
269            IndentingWriter fieldWriter = writer;
270            // the encoded fields are sorted, so we just have to compare with the previous one to detect duplicates
271            if (i > 0 && field.equals(encodedFields.get(i-1))) {
272                fieldWriter = new CommentingIndentingWriter(writer, "#");
273                fieldWriter.write("Ignoring field with duplicate signature\n");
274                System.err.println(String.format("Warning: class %s has duplicate instance field %s, Ignoring.",
275                        classDefItem.getClassType().getTypeDescriptor(), field.field.getShortFieldString()));
276            }
277
278			FieldDefinition.writeTo(fieldWriter, field, null, fieldAnnotations, false);
279        }
280    }
281
282    private void writeDirectMethods(IndentingWriter writer) throws IOException {
283        if (classDataItem == null) {
284            return;
285        }
286
287        List<ClassDataItem.EncodedMethod> directMethods = classDataItem.getDirectMethods();
288        if (directMethods.size() == 0) {
289            return;
290        }
291
292        writer.write("\n\n");
293        writer.write("# direct methods\n");
294        writeMethods(writer, directMethods);
295    }
296
297    private void writeVirtualMethods(IndentingWriter writer) throws IOException {
298        if (classDataItem == null) {
299            return;
300        }
301
302        List<ClassDataItem.EncodedMethod> virtualMethods = classDataItem.getVirtualMethods();
303
304        if (virtualMethods.size() == 0) {
305            return;
306        }
307
308        writer.write("\n\n");
309        writer.write("# virtual methods\n");
310        writeMethods(writer, virtualMethods);
311    }
312
313    private void writeMethods(IndentingWriter writer, List<ClassDataItem.EncodedMethod> methods) throws IOException {
314        for (int i=0; i<methods.size(); i++) {
315            ClassDataItem.EncodedMethod method = methods.get(i);
316            if (i > 0) {
317                writer.write('\n');
318            }
319
320            AnnotationSetItem methodAnnotations = null;
321            AnnotationSetRefList parameterAnnotations = null;
322            AnnotationDirectoryItem annotations = classDefItem.getAnnotations();
323            if (annotations != null) {
324                methodAnnotations = annotations.getMethodAnnotations(method.method);
325                parameterAnnotations = annotations.getParameterAnnotations(method.method);
326            }
327
328            IndentingWriter methodWriter = writer;
329            // the encoded methods are sorted, so we just have to compare with the previous one to detect duplicates
330            if (i > 0 && method.equals(methods.get(i-1))) {
331                methodWriter = new CommentingIndentingWriter(writer, "#");
332                methodWriter.write("Ignoring method with duplicate signature\n");
333                System.err.println(String.format("Warning: class %s has duplicate method %s, Ignoring.",
334                        classDefItem.getClassType().getTypeDescriptor(), method.method.getShortMethodString()));
335            }
336
337            MethodDefinition methodDefinition = new MethodDefinition(method);
338            methodDefinition.writeTo(methodWriter, methodAnnotations, parameterAnnotations);
339
340            ValidationException validationException = methodDefinition.getValidationException();
341            if (validationException != null) {
342                System.err.println(String.format("Error while disassembling method %s. Continuing.",
343                        method.method.getMethodString()));
344                validationException.printStackTrace(System.err);
345                this.validationErrors = true;
346            }
347        }
348    }
349}
350