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 org.jf.baksmali.BaksmaliOptions; 32import org.jf.dexlib2.AccessFlags; 33import org.jf.dexlib2.dexbacked.DexBackedClassDef; 34import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex; 35import org.jf.dexlib2.iface.*; 36import org.jf.dexlib2.iface.instruction.Instruction; 37import org.jf.dexlib2.iface.instruction.formats.Instruction21c; 38import org.jf.dexlib2.iface.reference.FieldReference; 39import org.jf.dexlib2.util.ReferenceUtil; 40import org.jf.util.IndentingWriter; 41import org.jf.util.StringUtils; 42 43import javax.annotation.Nonnull; 44import java.io.IOException; 45import java.util.*; 46 47public class ClassDefinition { 48 @Nonnull public final BaksmaliOptions options; 49 @Nonnull public final ClassDef classDef; 50 @Nonnull private final HashSet<String> fieldsSetInStaticConstructor; 51 52 protected boolean validationErrors; 53 54 public ClassDefinition(@Nonnull BaksmaliOptions options, @Nonnull ClassDef classDef) { 55 this.options = options; 56 this.classDef = classDef; 57 fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(classDef); 58 } 59 60 public boolean hadValidationErrors() { 61 return validationErrors; 62 } 63 64 @Nonnull 65 private static HashSet<String> findFieldsSetInStaticConstructor(@Nonnull ClassDef classDef) { 66 HashSet<String> fieldsSetInStaticConstructor = new HashSet<String>(); 67 68 for (Method method: classDef.getDirectMethods()) { 69 if (method.getName().equals("<clinit>")) { 70 MethodImplementation impl = method.getImplementation(); 71 if (impl != null) { 72 for (Instruction instruction: impl.getInstructions()) { 73 switch (instruction.getOpcode()) { 74 case SPUT: 75 case SPUT_BOOLEAN: 76 case SPUT_BYTE: 77 case SPUT_CHAR: 78 case SPUT_OBJECT: 79 case SPUT_SHORT: 80 case SPUT_WIDE: { 81 Instruction21c ins = (Instruction21c)instruction; 82 FieldReference fieldRef = null; 83 try { 84 fieldRef = (FieldReference)ins.getReference(); 85 } catch (InvalidItemIndex ex) { 86 // just ignore it for now. We'll deal with it later, when processing the instructions 87 // themselves 88 } 89 if (fieldRef != null && 90 fieldRef.getDefiningClass().equals((classDef.getType()))) { 91 fieldsSetInStaticConstructor.add(ReferenceUtil.getShortFieldDescriptor(fieldRef)); 92 } 93 break; 94 } 95 } 96 } 97 } 98 } 99 } 100 return fieldsSetInStaticConstructor; 101 } 102 103 public void writeTo(IndentingWriter writer) throws IOException { 104 writeClass(writer); 105 writeSuper(writer); 106 writeSourceFile(writer); 107 writeInterfaces(writer); 108 writeAnnotations(writer); 109 Set<String> staticFields = writeStaticFields(writer); 110 writeInstanceFields(writer, staticFields); 111 Set<String> directMethods = writeDirectMethods(writer); 112 writeVirtualMethods(writer, directMethods); 113 } 114 115 private void writeClass(IndentingWriter writer) throws IOException { 116 writer.write(".class "); 117 writeAccessFlags(writer); 118 writer.write(classDef.getType()); 119 writer.write('\n'); 120 } 121 122 private void writeAccessFlags(IndentingWriter writer) throws IOException { 123 for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForClass(classDef.getAccessFlags())) { 124 writer.write(accessFlag.toString()); 125 writer.write(' '); 126 } 127 } 128 129 private void writeSuper(IndentingWriter writer) throws IOException { 130 String superClass = classDef.getSuperclass(); 131 if (superClass != null) { 132 writer.write(".super "); 133 writer.write(superClass); 134 writer.write('\n'); 135 } 136 } 137 138 private void writeSourceFile(IndentingWriter writer) throws IOException { 139 String sourceFile = classDef.getSourceFile(); 140 if (sourceFile != null) { 141 writer.write(".source \""); 142 StringUtils.writeEscapedString(writer, sourceFile); 143 writer.write("\"\n"); 144 } 145 } 146 147 private void writeInterfaces(IndentingWriter writer) throws IOException { 148 List<String> interfaces = classDef.getInterfaces(); 149 150 if (interfaces.size() != 0) { 151 writer.write('\n'); 152 writer.write("# interfaces\n"); 153 for (String interfaceName: interfaces) { 154 writer.write(".implements "); 155 writer.write(interfaceName); 156 writer.write('\n'); 157 } 158 } 159 } 160 161 private void writeAnnotations(IndentingWriter writer) throws IOException { 162 Collection<? extends Annotation> classAnnotations = classDef.getAnnotations(); 163 if (classAnnotations.size() != 0) { 164 writer.write("\n\n"); 165 writer.write("# annotations\n"); 166 167 String containingClass = null; 168 if (options.implicitReferences) { 169 containingClass = classDef.getType(); 170 } 171 172 AnnotationFormatter.writeTo(writer, classAnnotations, containingClass); 173 } 174 } 175 176 private Set<String> writeStaticFields(IndentingWriter writer) throws IOException { 177 boolean wroteHeader = false; 178 Set<String> writtenFields = new HashSet<String>(); 179 180 Iterable<? extends Field> staticFields; 181 if (classDef instanceof DexBackedClassDef) { 182 staticFields = ((DexBackedClassDef)classDef).getStaticFields(false); 183 } else { 184 staticFields = classDef.getStaticFields(); 185 } 186 187 for (Field field: staticFields) { 188 if (!wroteHeader) { 189 writer.write("\n\n"); 190 writer.write("# static fields"); 191 wroteHeader = true; 192 } 193 writer.write('\n'); 194 195 boolean setInStaticConstructor; 196 IndentingWriter fieldWriter = writer; 197 String fieldString = ReferenceUtil.getShortFieldDescriptor(field); 198 if (!writtenFields.add(fieldString)) { 199 writer.write("# duplicate field ignored\n"); 200 fieldWriter = new CommentingIndentingWriter(writer); 201 System.err.println(String.format("Ignoring duplicate field: %s->%s", classDef.getType(), fieldString)); 202 setInStaticConstructor = false; 203 } else { 204 setInStaticConstructor = fieldsSetInStaticConstructor.contains(fieldString); 205 } 206 FieldDefinition.writeTo(options, fieldWriter, field, setInStaticConstructor); 207 } 208 return writtenFields; 209 } 210 211 private void writeInstanceFields(IndentingWriter writer, Set<String> staticFields) throws IOException { 212 boolean wroteHeader = false; 213 Set<String> writtenFields = new HashSet<String>(); 214 215 Iterable<? extends Field> instanceFields; 216 if (classDef instanceof DexBackedClassDef) { 217 instanceFields = ((DexBackedClassDef)classDef).getInstanceFields(false); 218 } else { 219 instanceFields = classDef.getInstanceFields(); 220 } 221 222 for (Field field: instanceFields) { 223 if (!wroteHeader) { 224 writer.write("\n\n"); 225 writer.write("# instance fields"); 226 wroteHeader = true; 227 } 228 writer.write('\n'); 229 230 IndentingWriter fieldWriter = writer; 231 String fieldString = ReferenceUtil.getShortFieldDescriptor(field); 232 if (!writtenFields.add(fieldString)) { 233 writer.write("# duplicate field ignored\n"); 234 fieldWriter = new CommentingIndentingWriter(writer); 235 System.err.println(String.format("Ignoring duplicate field: %s->%s", classDef.getType(), fieldString)); 236 } else if (staticFields.contains(fieldString)) { 237 System.err.println(String.format("Duplicate static+instance field found: %s->%s", 238 classDef.getType(), fieldString)); 239 System.err.println("You will need to rename one of these fields, including all references."); 240 241 writer.write("# There is both a static and instance field with this signature.\n" + 242 "# You will need to rename one of these fields, including all references.\n"); 243 } 244 FieldDefinition.writeTo(options, fieldWriter, field, false); 245 } 246 } 247 248 private Set<String> writeDirectMethods(IndentingWriter writer) throws IOException { 249 boolean wroteHeader = false; 250 Set<String> writtenMethods = new HashSet<String>(); 251 252 Iterable<? extends Method> directMethods; 253 if (classDef instanceof DexBackedClassDef) { 254 directMethods = ((DexBackedClassDef)classDef).getDirectMethods(false); 255 } else { 256 directMethods = classDef.getDirectMethods(); 257 } 258 259 for (Method method: directMethods) { 260 if (!wroteHeader) { 261 writer.write("\n\n"); 262 writer.write("# direct methods"); 263 wroteHeader = true; 264 } 265 writer.write('\n'); 266 267 // TODO: check for method validation errors 268 String methodString = ReferenceUtil.getMethodDescriptor(method, true); 269 270 IndentingWriter methodWriter = writer; 271 if (!writtenMethods.add(methodString)) { 272 writer.write("# duplicate method ignored\n"); 273 methodWriter = new CommentingIndentingWriter(writer); 274 } 275 276 MethodImplementation methodImpl = method.getImplementation(); 277 if (methodImpl == null) { 278 MethodDefinition.writeEmptyMethodTo(methodWriter, method, options); 279 } else { 280 MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl); 281 methodDefinition.writeTo(methodWriter); 282 } 283 } 284 return writtenMethods; 285 } 286 287 private void writeVirtualMethods(IndentingWriter writer, Set<String> directMethods) throws IOException { 288 boolean wroteHeader = false; 289 Set<String> writtenMethods = new HashSet<String>(); 290 291 Iterable<? extends Method> virtualMethods; 292 if (classDef instanceof DexBackedClassDef) { 293 virtualMethods = ((DexBackedClassDef)classDef).getVirtualMethods(false); 294 } else { 295 virtualMethods = classDef.getVirtualMethods(); 296 } 297 298 for (Method method: virtualMethods) { 299 if (!wroteHeader) { 300 writer.write("\n\n"); 301 writer.write("# virtual methods"); 302 wroteHeader = true; 303 } 304 writer.write('\n'); 305 306 // TODO: check for method validation errors 307 String methodString = ReferenceUtil.getMethodDescriptor(method, true); 308 309 IndentingWriter methodWriter = writer; 310 if (!writtenMethods.add(methodString)) { 311 writer.write("# duplicate method ignored\n"); 312 methodWriter = new CommentingIndentingWriter(writer); 313 } else if (directMethods.contains(methodString)) { 314 writer.write("# There is both a direct and virtual method with this signature.\n" + 315 "# You will need to rename one of these methods, including all references.\n"); 316 System.err.println(String.format("Duplicate direct+virtual method found: %s->%s", 317 classDef.getType(), methodString)); 318 System.err.println("You will need to rename one of these methods, including all references."); 319 } 320 321 MethodImplementation methodImpl = method.getImplementation(); 322 if (methodImpl == null) { 323 MethodDefinition.writeEmptyMethodTo(methodWriter, method, options); 324 } else { 325 MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl); 326 methodDefinition.writeTo(methodWriter); 327 } 328 } 329 } 330} 331