Home | History | Annotate | Download | only in Adaptors
      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