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