1 /* 2 * [The "BSD licence"] 3 * Copyright (c) 2010 Ben Gruver (JesusFreke) 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 package com.android.tools.smali.baksmali.Adaptors; 29 30 import com.android.tools.smali.dexlib2.iface.Annotation; 31 import com.android.tools.smali.dexlib2.iface.ClassDef; 32 import com.android.tools.smali.dexlib2.iface.Field; 33 import com.android.tools.smali.dexlib2.iface.Method; 34 import com.android.tools.smali.dexlib2.iface.MethodImplementation; 35 import com.android.tools.smali.baksmali.BaksmaliOptions; 36 import com.android.tools.smali.baksmali.formatter.BaksmaliFormatter; 37 import com.android.tools.smali.baksmali.formatter.BaksmaliWriter; 38 import com.android.tools.smali.dexlib2.AccessFlags; 39 import com.android.tools.smali.dexlib2.dexbacked.DexBackedClassDef; 40 import com.android.tools.smali.dexlib2.iface.instruction.Instruction; 41 import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c; 42 import com.android.tools.smali.dexlib2.iface.reference.FieldReference; 43 import com.android.tools.smali.dexlib2.iface.reference.Reference; 44 45 import javax.annotation.Nonnull; 46 import java.io.IOException; 47 import java.util.Collection; 48 import java.util.HashSet; 49 import java.util.List; 50 import java.util.Set; 51 52 public class ClassDefinition { 53 @Nonnull public final BaksmaliOptions options; 54 @Nonnull public final ClassDef classDef; 55 @Nonnull private final HashSet<String> fieldsSetInStaticConstructor; 56 @Nonnull private final BaksmaliFormatter formatter; 57 58 protected boolean validationErrors; 59 ClassDefinition(@onnull BaksmaliOptions options, @Nonnull ClassDef classDef)60 public ClassDefinition(@Nonnull BaksmaliOptions options, @Nonnull ClassDef classDef) { 61 this.options = options; 62 this.classDef = classDef; 63 formatter = new BaksmaliFormatter(options.implicitReferences ? classDef.getType() : null); 64 fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(classDef); 65 } 66 hadValidationErrors()67 public boolean hadValidationErrors() { 68 return validationErrors; 69 } 70 71 @Nonnull findFieldsSetInStaticConstructor(@onnull ClassDef classDef)72 private HashSet<String> findFieldsSetInStaticConstructor(@Nonnull ClassDef classDef) { 73 HashSet<String> fieldsSetInStaticConstructor = new HashSet<String>(); 74 75 for (Method method: classDef.getDirectMethods()) { 76 if (method.getName().equals("<clinit>")) { 77 MethodImplementation impl = method.getImplementation(); 78 if (impl != null) { 79 for (Instruction instruction: impl.getInstructions()) { 80 switch (instruction.getOpcode()) { 81 case SPUT: 82 case SPUT_BOOLEAN: 83 case SPUT_BYTE: 84 case SPUT_CHAR: 85 case SPUT_OBJECT: 86 case SPUT_SHORT: 87 case SPUT_WIDE: { 88 Instruction21c ins = (Instruction21c)instruction; 89 FieldReference fieldRef = (FieldReference)ins.getReference(); 90 try { 91 fieldRef.validateReference(); 92 if (fieldRef.getDefiningClass().equals((classDef.getType()))) { 93 fieldsSetInStaticConstructor.add( 94 formatter.getShortFieldDescriptor(fieldRef)); 95 } 96 } catch (Reference.InvalidReferenceException ex) { 97 // Just ignore for now. We'll deal with it when processing the instruction 98 } 99 break; 100 } 101 } 102 } 103 } 104 } 105 } 106 return fieldsSetInStaticConstructor; 107 } 108 writeTo(BaksmaliWriter writer)109 public void writeTo(BaksmaliWriter writer) throws IOException { 110 writeClass(writer); 111 writeSuper(writer); 112 writeSourceFile(writer); 113 writeInterfaces(writer); 114 writeAnnotations(writer); 115 Set<String> staticFields = writeStaticFields(writer); 116 writeInstanceFields(writer, staticFields); 117 Set<String> directMethods = writeDirectMethods(writer); 118 writeVirtualMethods(writer, directMethods); 119 } 120 writeClass(BaksmaliWriter writer)121 private void writeClass(BaksmaliWriter writer) throws IOException { 122 writer.write(".class "); 123 writeAccessFlags(writer); 124 writer.writeType(classDef.getType()); 125 writer.write('\n'); 126 } 127 writeAccessFlags(BaksmaliWriter writer)128 private void writeAccessFlags(BaksmaliWriter writer) throws IOException { 129 for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForClass(classDef.getAccessFlags())) { 130 writer.write(accessFlag.toString()); 131 writer.write(' '); 132 } 133 } 134 writeSuper(BaksmaliWriter writer)135 private void writeSuper(BaksmaliWriter writer) throws IOException { 136 String superClass = classDef.getSuperclass(); 137 if (superClass != null) { 138 writer.write(".super "); 139 writer.writeType(superClass); 140 writer.write('\n'); 141 } 142 } 143 writeSourceFile(BaksmaliWriter writer)144 private void writeSourceFile(BaksmaliWriter writer) throws IOException { 145 String sourceFile = classDef.getSourceFile(); 146 if (sourceFile != null) { 147 writer.write(".source "); 148 writer.writeQuotedString(sourceFile); 149 writer.write("\n"); 150 } 151 } 152 writeInterfaces(BaksmaliWriter writer)153 private void writeInterfaces(BaksmaliWriter writer) throws IOException { 154 List<String> interfaces = classDef.getInterfaces(); 155 156 if (interfaces.size() != 0) { 157 writer.write('\n'); 158 writer.write("# interfaces\n"); 159 for (String interfaceName: interfaces) { 160 writer.write(".implements "); 161 writer.writeType(interfaceName); 162 writer.write('\n'); 163 } 164 } 165 } 166 writeAnnotations(BaksmaliWriter writer)167 private void writeAnnotations(BaksmaliWriter writer) throws IOException { 168 Collection<? extends Annotation> classAnnotations = classDef.getAnnotations(); 169 if (classAnnotations.size() != 0) { 170 writer.write("\n\n"); 171 writer.write("# annotations\n"); 172 173 AnnotationFormatter.writeTo(writer, classAnnotations); 174 } 175 } 176 writeStaticFields(BaksmaliWriter writer)177 private Set<String> writeStaticFields(BaksmaliWriter 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 BaksmaliWriter fieldWriter = writer; 198 String fieldString = formatter.getShortFieldDescriptor(field); 199 if (!writtenFields.add(fieldString)) { 200 writer.write("# duplicate field ignored\n"); 201 fieldWriter = getCommentingWriter(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(fieldWriter, field, setInStaticConstructor); 208 } 209 return writtenFields; 210 } 211 writeInstanceFields(BaksmaliWriter writer, Set<String> staticFields)212 private void writeInstanceFields(BaksmaliWriter 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 BaksmaliWriter fieldWriter = writer; 232 String fieldString = formatter.getShortFieldDescriptor(field); 233 if (!writtenFields.add(fieldString)) { 234 writer.write("# duplicate field ignored\n"); 235 fieldWriter = getCommentingWriter(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(fieldWriter, field, false); 246 } 247 } 248 writeDirectMethods(BaksmaliWriter writer)249 private Set<String> writeDirectMethods(BaksmaliWriter 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 = formatter.getShortMethodDescriptor(method); 270 271 BaksmaliWriter methodWriter = writer; 272 if (!writtenMethods.add(methodString)) { 273 writer.write("# duplicate method ignored\n"); 274 methodWriter = getCommentingWriter(writer); 275 } 276 277 MethodImplementation methodImpl = method.getImplementation(); 278 if (methodImpl == null) { 279 MethodDefinition.writeEmptyMethodTo(methodWriter, method, this); 280 } else { 281 MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl); 282 methodDefinition.writeTo(methodWriter); 283 } 284 } 285 return writtenMethods; 286 } 287 writeVirtualMethods(BaksmaliWriter writer, Set<String> directMethods)288 private void writeVirtualMethods(BaksmaliWriter writer, Set<String> directMethods) 289 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 = formatter.getShortMethodDescriptor(method); 310 311 BaksmaliWriter methodWriter = writer; 312 if (!writtenMethods.add(methodString)) { 313 writer.write("# duplicate method ignored\n"); 314 methodWriter = getCommentingWriter(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, this); 326 } else { 327 MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl); 328 methodDefinition.writeTo(methodWriter); 329 } 330 } 331 } 332 getCommentingWriter(BaksmaliWriter writer)333 public BaksmaliWriter getCommentingWriter(BaksmaliWriter writer) { 334 return formatter.getWriter(new CommentingIndentingWriter(writer.indentingWriter())); 335 } 336 getFormatter()337 public BaksmaliFormatter getFormatter() { 338 return formatter; 339 } 340 } 341