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 = classDef.getInterfaces();
150
151        if (interfaces.size() != 0) {
152            writer.write('\n');
153            writer.write("# interfaces\n");
154            for (String interfaceName: interfaces) {
155                writer.write(".implements ");
156                writer.write(interfaceName);
157                writer.write('\n');
158            }
159        }
160    }
161
162    private void writeAnnotations(IndentingWriter writer) throws IOException {
163        Collection<? extends Annotation> classAnnotations = classDef.getAnnotations();
164        if (classAnnotations.size() != 0) {
165            writer.write("\n\n");
166            writer.write("# annotations\n");
167
168            String containingClass = null;
169            if (options.useImplicitReferences) {
170                containingClass = classDef.getType();
171            }
172
173            AnnotationFormatter.writeTo(writer, classAnnotations, containingClass);
174        }
175    }
176
177    private Set<String> writeStaticFields(IndentingWriter writer) throws IOException {
178        boolean wroteHeader = false;
179        Set<String> writtenFields = new HashSet<String>();
180
181        Iterable<? extends Field> staticFields;
182        if (classDef instanceof DexBackedClassDef) {
183            staticFields = ((DexBackedClassDef)classDef).getStaticFields(false);
184        } else {
185            staticFields = classDef.getStaticFields();
186        }
187
188        for (Field field: staticFields) {
189            if (!wroteHeader) {
190                writer.write("\n\n");
191                writer.write("# static fields");
192                wroteHeader = true;
193            }
194            writer.write('\n');
195
196            boolean setInStaticConstructor;
197            IndentingWriter fieldWriter = writer;
198            String fieldString = ReferenceUtil.getShortFieldDescriptor(field);
199            if (!writtenFields.add(fieldString)) {
200                writer.write("# duplicate field ignored\n");
201                fieldWriter = new CommentingIndentingWriter(writer);
202                System.err.println(String.format("Ignoring duplicate field: %s->%s", classDef.getType(), fieldString));
203                setInStaticConstructor = false;
204            } else {
205                setInStaticConstructor = fieldsSetInStaticConstructor.contains(fieldString);
206            }
207            FieldDefinition.writeTo(options, fieldWriter, field, setInStaticConstructor);
208        }
209        return writtenFields;
210    }
211
212    private void writeInstanceFields(IndentingWriter writer, Set<String> staticFields) throws IOException {
213        boolean wroteHeader = false;
214        Set<String> writtenFields = new HashSet<String>();
215
216        Iterable<? extends Field> instanceFields;
217        if (classDef instanceof DexBackedClassDef) {
218            instanceFields = ((DexBackedClassDef)classDef).getInstanceFields(false);
219        } else {
220            instanceFields = classDef.getInstanceFields();
221        }
222
223        for (Field field: instanceFields) {
224            if (!wroteHeader) {
225                writer.write("\n\n");
226                writer.write("# instance fields");
227                wroteHeader = true;
228            }
229            writer.write('\n');
230
231            IndentingWriter fieldWriter = writer;
232            String fieldString = ReferenceUtil.getShortFieldDescriptor(field);
233            if (!writtenFields.add(fieldString)) {
234                writer.write("# duplicate field ignored\n");
235                fieldWriter = new CommentingIndentingWriter(writer);
236                System.err.println(String.format("Ignoring duplicate field: %s->%s", classDef.getType(), fieldString));
237            } else if (staticFields.contains(fieldString)) {
238                System.err.println(String.format("Duplicate static+instance field found: %s->%s",
239                        classDef.getType(), fieldString));
240                System.err.println("You will need to rename one of these fields, including all references.");
241
242                writer.write("# There is both a static and instance field with this signature.\n" +
243                             "# You will need to rename one of these fields, including all references.\n");
244            }
245            FieldDefinition.writeTo(options, fieldWriter, field, false);
246        }
247    }
248
249    private Set<String> writeDirectMethods(IndentingWriter writer) throws IOException {
250        boolean wroteHeader = false;
251        Set<String> writtenMethods = new HashSet<String>();
252
253        Iterable<? extends Method> directMethods;
254        if (classDef instanceof DexBackedClassDef) {
255            directMethods = ((DexBackedClassDef)classDef).getDirectMethods(false);
256        } else {
257            directMethods = classDef.getDirectMethods();
258        }
259
260        for (Method method: directMethods) {
261            if (!wroteHeader) {
262                writer.write("\n\n");
263                writer.write("# direct methods");
264                wroteHeader = true;
265            }
266            writer.write('\n');
267
268            // TODO: check for method validation errors
269            String methodString = ReferenceUtil.getMethodDescriptor(method, true);
270
271            IndentingWriter methodWriter = writer;
272            if (!writtenMethods.add(methodString)) {
273                writer.write("# duplicate method ignored\n");
274                methodWriter = new CommentingIndentingWriter(writer);
275            }
276
277            MethodImplementation methodImpl = method.getImplementation();
278            if (methodImpl == null) {
279                MethodDefinition.writeEmptyMethodTo(methodWriter, method, options);
280            } else {
281                MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl);
282                methodDefinition.writeTo(methodWriter);
283            }
284        }
285        return writtenMethods;
286    }
287
288    private void writeVirtualMethods(IndentingWriter writer, Set<String> directMethods) throws IOException {
289        boolean wroteHeader = false;
290        Set<String> writtenMethods = new HashSet<String>();
291
292        Iterable<? extends Method> virtualMethods;
293        if (classDef instanceof DexBackedClassDef) {
294            virtualMethods = ((DexBackedClassDef)classDef).getVirtualMethods(false);
295        } else {
296            virtualMethods = classDef.getVirtualMethods();
297        }
298
299        for (Method method: virtualMethods) {
300            if (!wroteHeader) {
301                writer.write("\n\n");
302                writer.write("# virtual methods");
303                wroteHeader = true;
304            }
305            writer.write('\n');
306
307            // TODO: check for method validation errors
308            String methodString = ReferenceUtil.getMethodDescriptor(method, true);
309
310            IndentingWriter methodWriter = writer;
311            if (!writtenMethods.add(methodString)) {
312                writer.write("# duplicate method ignored\n");
313                methodWriter = new CommentingIndentingWriter(writer);
314            } else if (directMethods.contains(methodString)) {
315                writer.write("# There is both a direct and virtual method with this signature.\n" +
316                             "# You will need to rename one of these methods, including all references.\n");
317                System.err.println(String.format("Duplicate direct+virtual method found: %s->%s",
318                        classDef.getType(), methodString));
319                System.err.println("You will need to rename one of these methods, including all references.");
320            }
321
322            MethodImplementation methodImpl = method.getImplementation();
323            if (methodImpl == null) {
324                MethodDefinition.writeEmptyMethodTo(methodWriter, method, options);
325            } else {
326                MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl);
327                methodDefinition.writeTo(methodWriter);
328            }
329        }
330    }
331}
332