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 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