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.baksmali.BaksmaliOptions;
32import org.jf.dexlib2.AccessFlags;
33import org.jf.dexlib2.dexbacked.DexBackedClassDef;
34import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex;
35import org.jf.dexlib2.iface.*;
36import org.jf.dexlib2.iface.instruction.Instruction;
37import org.jf.dexlib2.iface.instruction.formats.Instruction21c;
38import org.jf.dexlib2.iface.reference.FieldReference;
39import org.jf.dexlib2.util.ReferenceUtil;
40import org.jf.util.IndentingWriter;
41import org.jf.util.StringUtils;
42
43import javax.annotation.Nonnull;
44import java.io.IOException;
45import java.util.*;
46
47public class ClassDefinition {
48    @Nonnull public final BaksmaliOptions options;
49    @Nonnull public final ClassDef classDef;
50    @Nonnull private final HashSet<String> fieldsSetInStaticConstructor;
51
52    protected boolean validationErrors;
53
54    public ClassDefinition(@Nonnull BaksmaliOptions options, @Nonnull ClassDef classDef) {
55        this.options = options;
56        this.classDef = classDef;
57        fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(classDef);
58    }
59
60    public boolean hadValidationErrors() {
61        return validationErrors;
62    }
63
64    @Nonnull
65    private static HashSet<String> findFieldsSetInStaticConstructor(@Nonnull ClassDef classDef) {
66        HashSet<String> fieldsSetInStaticConstructor = new HashSet<String>();
67
68        for (Method method: classDef.getDirectMethods()) {
69            if (method.getName().equals("<clinit>")) {
70                MethodImplementation impl = method.getImplementation();
71                if (impl != null) {
72                    for (Instruction instruction: impl.getInstructions()) {
73                        switch (instruction.getOpcode()) {
74                            case SPUT:
75                            case SPUT_BOOLEAN:
76                            case SPUT_BYTE:
77                            case SPUT_CHAR:
78                            case SPUT_OBJECT:
79                            case SPUT_SHORT:
80                            case SPUT_WIDE: {
81                                Instruction21c ins = (Instruction21c)instruction;
82                                FieldReference fieldRef = null;
83                                try {
84                                    fieldRef = (FieldReference)ins.getReference();
85                                } catch (InvalidItemIndex ex) {
86                                    // just ignore it for now. We'll deal with it later, when processing the instructions
87                                    // themselves
88                                }
89                                if (fieldRef != null &&
90                                        fieldRef.getDefiningClass().equals((classDef.getType()))) {
91                                    fieldsSetInStaticConstructor.add(ReferenceUtil.getShortFieldDescriptor(fieldRef));
92                                }
93                                break;
94                            }
95                        }
96                    }
97                }
98            }
99        }
100        return fieldsSetInStaticConstructor;
101    }
102
103    public void writeTo(IndentingWriter writer) throws IOException {
104        writeClass(writer);
105        writeSuper(writer);
106        writeSourceFile(writer);
107        writeInterfaces(writer);
108        writeAnnotations(writer);
109        Set<String> staticFields = writeStaticFields(writer);
110        writeInstanceFields(writer, staticFields);
111        Set<String> directMethods = writeDirectMethods(writer);
112        writeVirtualMethods(writer, directMethods);
113    }
114
115    private void writeClass(IndentingWriter writer) throws IOException {
116        writer.write(".class ");
117        writeAccessFlags(writer);
118        writer.write(classDef.getType());
119        writer.write('\n');
120    }
121
122    private void writeAccessFlags(IndentingWriter writer) throws IOException {
123        for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForClass(classDef.getAccessFlags())) {
124            writer.write(accessFlag.toString());
125            writer.write(' ');
126        }
127    }
128
129    private void writeSuper(IndentingWriter writer) throws IOException {
130        String superClass = classDef.getSuperclass();
131        if (superClass != null) {
132            writer.write(".super ");
133            writer.write(superClass);
134            writer.write('\n');
135        }
136    }
137
138    private void writeSourceFile(IndentingWriter writer) throws IOException {
139        String sourceFile = classDef.getSourceFile();
140        if (sourceFile != null) {
141            writer.write(".source \"");
142            StringUtils.writeEscapedString(writer, sourceFile);
143            writer.write("\"\n");
144        }
145    }
146
147    private void writeInterfaces(IndentingWriter writer) throws IOException {
148        List<String> interfaces = classDef.getInterfaces();
149
150        if (interfaces.size() != 0) {
151            writer.write('\n');
152            writer.write("# interfaces\n");
153            for (String interfaceName: interfaces) {
154                writer.write(".implements ");
155                writer.write(interfaceName);
156                writer.write('\n');
157            }
158        }
159    }
160
161    private void writeAnnotations(IndentingWriter writer) throws IOException {
162        Collection<? extends Annotation> classAnnotations = classDef.getAnnotations();
163        if (classAnnotations.size() != 0) {
164            writer.write("\n\n");
165            writer.write("# annotations\n");
166
167            String containingClass = null;
168            if (options.implicitReferences) {
169                containingClass = classDef.getType();
170            }
171
172            AnnotationFormatter.writeTo(writer, classAnnotations, containingClass);
173        }
174    }
175
176    private Set<String> writeStaticFields(IndentingWriter writer) throws IOException {
177        boolean wroteHeader = false;
178        Set<String> writtenFields = new HashSet<String>();
179
180        Iterable<? extends Field> staticFields;
181        if (classDef instanceof DexBackedClassDef) {
182            staticFields = ((DexBackedClassDef)classDef).getStaticFields(false);
183        } else {
184            staticFields = classDef.getStaticFields();
185        }
186
187        for (Field field: staticFields) {
188            if (!wroteHeader) {
189                writer.write("\n\n");
190                writer.write("# static fields");
191                wroteHeader = true;
192            }
193            writer.write('\n');
194
195            boolean setInStaticConstructor;
196            IndentingWriter fieldWriter = writer;
197            String fieldString = ReferenceUtil.getShortFieldDescriptor(field);
198            if (!writtenFields.add(fieldString)) {
199                writer.write("# duplicate field ignored\n");
200                fieldWriter = new CommentingIndentingWriter(writer);
201                System.err.println(String.format("Ignoring duplicate field: %s->%s", classDef.getType(), fieldString));
202                setInStaticConstructor = false;
203            } else {
204                setInStaticConstructor = fieldsSetInStaticConstructor.contains(fieldString);
205            }
206            FieldDefinition.writeTo(options, fieldWriter, field, setInStaticConstructor);
207        }
208        return writtenFields;
209    }
210
211    private void writeInstanceFields(IndentingWriter writer, Set<String> staticFields) throws IOException {
212        boolean wroteHeader = false;
213        Set<String> writtenFields = new HashSet<String>();
214
215        Iterable<? extends Field> instanceFields;
216        if (classDef instanceof DexBackedClassDef) {
217            instanceFields = ((DexBackedClassDef)classDef).getInstanceFields(false);
218        } else {
219            instanceFields = classDef.getInstanceFields();
220        }
221
222        for (Field field: instanceFields) {
223            if (!wroteHeader) {
224                writer.write("\n\n");
225                writer.write("# instance fields");
226                wroteHeader = true;
227            }
228            writer.write('\n');
229
230            IndentingWriter fieldWriter = writer;
231            String fieldString = ReferenceUtil.getShortFieldDescriptor(field);
232            if (!writtenFields.add(fieldString)) {
233                writer.write("# duplicate field ignored\n");
234                fieldWriter = new CommentingIndentingWriter(writer);
235                System.err.println(String.format("Ignoring duplicate field: %s->%s", classDef.getType(), fieldString));
236            } else if (staticFields.contains(fieldString)) {
237                System.err.println(String.format("Duplicate static+instance field found: %s->%s",
238                        classDef.getType(), fieldString));
239                System.err.println("You will need to rename one of these fields, including all references.");
240
241                writer.write("# There is both a static and instance field with this signature.\n" +
242                             "# You will need to rename one of these fields, including all references.\n");
243            }
244            FieldDefinition.writeTo(options, fieldWriter, field, false);
245        }
246    }
247
248    private Set<String> writeDirectMethods(IndentingWriter writer) throws IOException {
249        boolean wroteHeader = false;
250        Set<String> writtenMethods = new HashSet<String>();
251
252        Iterable<? extends Method> directMethods;
253        if (classDef instanceof DexBackedClassDef) {
254            directMethods = ((DexBackedClassDef)classDef).getDirectMethods(false);
255        } else {
256            directMethods = classDef.getDirectMethods();
257        }
258
259        for (Method method: directMethods) {
260            if (!wroteHeader) {
261                writer.write("\n\n");
262                writer.write("# direct methods");
263                wroteHeader = true;
264            }
265            writer.write('\n');
266
267            // TODO: check for method validation errors
268            String methodString = ReferenceUtil.getMethodDescriptor(method, true);
269
270            IndentingWriter methodWriter = writer;
271            if (!writtenMethods.add(methodString)) {
272                writer.write("# duplicate method ignored\n");
273                methodWriter = new CommentingIndentingWriter(writer);
274            }
275
276            MethodImplementation methodImpl = method.getImplementation();
277            if (methodImpl == null) {
278                MethodDefinition.writeEmptyMethodTo(methodWriter, method, options);
279            } else {
280                MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl);
281                methodDefinition.writeTo(methodWriter);
282            }
283        }
284        return writtenMethods;
285    }
286
287    private void writeVirtualMethods(IndentingWriter writer, Set<String> directMethods) throws IOException {
288        boolean wroteHeader = false;
289        Set<String> writtenMethods = new HashSet<String>();
290
291        Iterable<? extends Method> virtualMethods;
292        if (classDef instanceof DexBackedClassDef) {
293            virtualMethods = ((DexBackedClassDef)classDef).getVirtualMethods(false);
294        } else {
295            virtualMethods = classDef.getVirtualMethods();
296        }
297
298        for (Method method: virtualMethods) {
299            if (!wroteHeader) {
300                writer.write("\n\n");
301                writer.write("# virtual methods");
302                wroteHeader = true;
303            }
304            writer.write('\n');
305
306            // TODO: check for method validation errors
307            String methodString = ReferenceUtil.getMethodDescriptor(method, true);
308
309            IndentingWriter methodWriter = writer;
310            if (!writtenMethods.add(methodString)) {
311                writer.write("# duplicate method ignored\n");
312                methodWriter = new CommentingIndentingWriter(writer);
313            } else if (directMethods.contains(methodString)) {
314                writer.write("# There is both a direct and virtual method with this signature.\n" +
315                             "# You will need to rename one of these methods, including all references.\n");
316                System.err.println(String.format("Duplicate direct+virtual method found: %s->%s",
317                        classDef.getType(), methodString));
318                System.err.println("You will need to rename one of these methods, including all references.");
319            }
320
321            MethodImplementation methodImpl = method.getImplementation();
322            if (methodImpl == null) {
323                MethodDefinition.writeEmptyMethodTo(methodWriter, method, options);
324            } else {
325                MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl);
326                methodDefinition.writeTo(methodWriter);
327            }
328        }
329    }
330}
331