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