• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
29 package org.jf.baksmali.Adaptors;
30 
31 import com.google.common.collect.Lists;
32 import org.jf.baksmali.baksmaliOptions;
33 import org.jf.dexlib2.AccessFlags;
34 import org.jf.dexlib2.dexbacked.DexBackedClassDef;
35 import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex;
36 import org.jf.dexlib2.iface.*;
37 import org.jf.dexlib2.iface.instruction.Instruction;
38 import org.jf.dexlib2.iface.instruction.formats.Instruction21c;
39 import org.jf.dexlib2.iface.reference.FieldReference;
40 import org.jf.dexlib2.util.ReferenceUtil;
41 import org.jf.util.IndentingWriter;
42 import org.jf.util.StringUtils;
43 
44 import javax.annotation.Nonnull;
45 import java.io.IOException;
46 import java.util.*;
47 
48 public 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 
ClassDefinition(@onnull baksmaliOptions options, @Nonnull ClassDef classDef)55     public ClassDefinition(@Nonnull baksmaliOptions options, @Nonnull ClassDef classDef) {
56         this.options = options;
57         this.classDef = classDef;
58         fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor();
59     }
60 
hadValidationErrors()61     public boolean hadValidationErrors() {
62         return validationErrors;
63     }
64 
65     @Nonnull
findFieldsSetInStaticConstructor()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 
writeTo(IndentingWriter writer)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 
writeClass(IndentingWriter writer)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 
writeAccessFlags(IndentingWriter writer)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 
writeSuper(IndentingWriter writer)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 
writeSourceFile(IndentingWriter writer)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 
writeInterfaces(IndentingWriter writer)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 
writeAnnotations(IndentingWriter writer)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 
writeStaticFields(IndentingWriter writer)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 
writeInstanceFields(IndentingWriter writer, Set<String> staticFields)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 
writeDirectMethods(IndentingWriter writer)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 
writeVirtualMethods(IndentingWriter writer, Set<String> directMethods)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