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