Home | History | Annotate | Download | only in program
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package dexfuzz.program;
     18 
     19 import dexfuzz.Log;
     20 import dexfuzz.MutationStats;
     21 import dexfuzz.Options;
     22 import dexfuzz.listeners.BaseListener;
     23 import dexfuzz.program.mutators.ArithOpChanger;
     24 import dexfuzz.program.mutators.BranchShifter;
     25 import dexfuzz.program.mutators.CmpBiasChanger;
     26 import dexfuzz.program.mutators.CodeMutator;
     27 import dexfuzz.program.mutators.ConstantValueChanger;
     28 import dexfuzz.program.mutators.ConversionRepeater;
     29 import dexfuzz.program.mutators.FieldFlagChanger;
     30 import dexfuzz.program.mutators.InstructionDeleter;
     31 import dexfuzz.program.mutators.InstructionDuplicator;
     32 import dexfuzz.program.mutators.InstructionSwapper;
     33 import dexfuzz.program.mutators.InvokeChanger;
     34 import dexfuzz.program.mutators.NewArrayLengthChanger;
     35 import dexfuzz.program.mutators.NewInstanceChanger;
     36 import dexfuzz.program.mutators.NewMethodCaller;
     37 import dexfuzz.program.mutators.NonsenseStringPrinter;
     38 import dexfuzz.program.mutators.OppositeBranchChanger;
     39 import dexfuzz.program.mutators.PoolIndexChanger;
     40 import dexfuzz.program.mutators.RandomBranchChanger;
     41 import dexfuzz.program.mutators.RandomInstructionGenerator;
     42 import dexfuzz.program.mutators.RegisterClobber;
     43 import dexfuzz.program.mutators.SwitchBranchShifter;
     44 import dexfuzz.program.mutators.TryBlockShifter;
     45 import dexfuzz.program.mutators.ValuePrinter;
     46 import dexfuzz.program.mutators.VRegChanger;
     47 import dexfuzz.rawdex.ClassDataItem;
     48 import dexfuzz.rawdex.ClassDefItem;
     49 import dexfuzz.rawdex.CodeItem;
     50 import dexfuzz.rawdex.DexRandomAccessFile;
     51 import dexfuzz.rawdex.EncodedField;
     52 import dexfuzz.rawdex.EncodedMethod;
     53 import dexfuzz.rawdex.FieldIdItem;
     54 import dexfuzz.rawdex.MethodIdItem;
     55 import dexfuzz.rawdex.ProtoIdItem;
     56 import dexfuzz.rawdex.RawDexFile;
     57 import dexfuzz.rawdex.TypeIdItem;
     58 import dexfuzz.rawdex.TypeList;
     59 import dexfuzz.rawdex.formats.ContainsPoolIndex.PoolIndexKind;
     60 
     61 import java.io.BufferedReader;
     62 import java.io.BufferedWriter;
     63 import java.io.FileReader;
     64 import java.io.FileWriter;
     65 import java.io.IOException;
     66 import java.util.ArrayList;
     67 import java.util.HashMap;
     68 import java.util.List;
     69 import java.util.Map;
     70 import java.util.Random;
     71 
     72 /**
     73  * After the raw DEX file has been parsed, it is passed into this class
     74  * that represents the program in a mutatable form.
     75  * The class uses a CodeTranslator to translate between the raw DEX form
     76  * for a method, and the mutatable form. It also controls all CodeMutators,
     77  * deciding which ones should be applied to each CodeItem.
     78  */
     79 public class Program {
     80   /**
     81    * The RNG used during mutation.
     82    */
     83   private Random rng;
     84 
     85   /**
     86    * The seed that was given to the RNG.
     87    */
     88   public long rngSeed;
     89 
     90   /**
     91    * The parsed raw DEX file.
     92    */
     93   private RawDexFile rawDexFile;
     94 
     95   /**
     96    * The system responsible for translating from CodeItems to MutatableCode and vice-versa.
     97    */
     98   private CodeTranslator translator;
     99 
    100   /**
    101    * Responsible for adding new class ID items, method ID items, etc.
    102    */
    103   private IdCreator idCreator;
    104 
    105   /**
    106    * A list of all the MutatableCode that the CodeTranslator produced from
    107    * CodeItems that are acceptable to mutate.
    108    */
    109   private List<MutatableCode> mutatableCodes;
    110 
    111   /**
    112    * A list of all MutatableCode items that were mutated when mutateTheProgram()
    113    * was called. updateRawDexFile() will update the relevant CodeItems when called,
    114    * and then clear this list.
    115    */
    116   private List<MutatableCode> mutatedCodes;
    117 
    118   /**
    119    * A list of all registered CodeMutators that this Program can use to mutate methods.
    120    */
    121   private List<CodeMutator> mutators;
    122 
    123   /**
    124    * Used if we're loading mutations from a file, so we can find the correct mutator.
    125    */
    126   private Map<Class<? extends CodeMutator>, CodeMutator> mutatorsLookupByClass;
    127 
    128   /**
    129    * Tracks mutation stats.
    130    */
    131   private MutationStats mutationStats;
    132 
    133   /**
    134    * A list of all mutations used for loading/dumping mutations from/to a file.
    135    */
    136   private List<Mutation> mutations;
    137 
    138   /**
    139    * The listener who is interested in events.
    140    * May be a listener that is responsible for multiple listeners.
    141    */
    142   private BaseListener listener;
    143 
    144   /**
    145    * Given a maximum number of mutations that can be performed on a method, n,
    146    * give up after attempting (n * this value) mutations for any method.
    147    */
    148   private static final int MAXIMUM_MUTATION_ATTEMPT_FACTOR = 10;
    149 
    150   /**
    151    * Construct the mutatable Program based on the raw DEX file that was parsed initially.
    152    */
    153   public Program(RawDexFile rawDexFile, List<Mutation> previousMutations,
    154       BaseListener listener) {
    155     this.listener = listener;
    156 
    157     idCreator = new IdCreator(rawDexFile);
    158 
    159     // Set up the RNG.
    160     rng = new Random();
    161     if (Options.usingProvidedSeed) {
    162       rng.setSeed(Options.rngSeed);
    163       rngSeed = Options.rngSeed;
    164     } else {
    165       long seed = System.currentTimeMillis();
    166       listener.handleSeed(seed);
    167       rng.setSeed(seed);
    168       rngSeed = seed;
    169     }
    170 
    171     if (previousMutations != null) {
    172       mutations = previousMutations;
    173     } else {
    174       // Allocate the mutations list.
    175       mutations = new ArrayList<Mutation>();
    176 
    177       // Read in the mutations if we need to.
    178       if (Options.loadMutations) {
    179         // Allocate the mutators lookup table.
    180         mutatorsLookupByClass = new HashMap<Class<? extends CodeMutator>, CodeMutator>();
    181         loadMutationsFromDisk(Options.loadMutationsFile);
    182       }
    183     }
    184 
    185     // Allocate the mutators list.
    186     mutators = new ArrayList<CodeMutator>();
    187 
    188     this.rawDexFile = rawDexFile;
    189 
    190     mutatableCodes = new ArrayList<MutatableCode>();
    191     mutatedCodes = new ArrayList<MutatableCode>();
    192 
    193     translator = new CodeTranslator();
    194 
    195     mutationStats = new MutationStats();
    196 
    197     // Register all the code mutators here.
    198     registerMutator(new ArithOpChanger(rng, mutationStats, mutations));
    199     registerMutator(new BranchShifter(rng, mutationStats, mutations));
    200     registerMutator(new CmpBiasChanger(rng, mutationStats, mutations));
    201     registerMutator(new ConstantValueChanger(rng, mutationStats, mutations));
    202     registerMutator(new ConversionRepeater(rng, mutationStats, mutations));
    203     registerMutator(new FieldFlagChanger(rng, mutationStats, mutations));
    204     registerMutator(new InstructionDeleter(rng, mutationStats, mutations));
    205     registerMutator(new InstructionDuplicator(rng, mutationStats, mutations));
    206     registerMutator(new InstructionSwapper(rng, mutationStats, mutations));
    207     registerMutator(new InvokeChanger(rng, mutationStats, mutations));
    208     registerMutator(new NewArrayLengthChanger(rng, mutationStats, mutations));
    209     registerMutator(new NewInstanceChanger(rng, mutationStats, mutations));
    210     registerMutator(new NewMethodCaller(rng, mutationStats, mutations));
    211     registerMutator(new NonsenseStringPrinter(rng, mutationStats, mutations));
    212     registerMutator(new OppositeBranchChanger(rng, mutationStats, mutations));
    213     registerMutator(new PoolIndexChanger(rng, mutationStats, mutations));
    214     registerMutator(new RandomBranchChanger(rng, mutationStats, mutations));
    215     registerMutator(new RandomInstructionGenerator(rng, mutationStats, mutations));
    216     registerMutator(new RegisterClobber(rng, mutationStats, mutations));
    217     registerMutator(new SwitchBranchShifter(rng, mutationStats, mutations));
    218     registerMutator(new TryBlockShifter(rng, mutationStats, mutations));
    219     registerMutator(new ValuePrinter(rng, mutationStats, mutations));
    220     registerMutator(new VRegChanger(rng, mutationStats, mutations));
    221 
    222     associateClassDefsAndClassData();
    223     associateCodeItemsWithMethodNames();
    224 
    225     int codeItemIdx = 0;
    226     for (CodeItem codeItem : rawDexFile.codeItems) {
    227       if (legalToMutate(codeItem)) {
    228         Log.debug("Legal to mutate code item " + codeItemIdx);
    229         int mutatableCodeIdx = mutatableCodes.size();
    230         mutatableCodes.add(translator.codeItemToMutatableCode(this, codeItem,
    231             codeItemIdx, mutatableCodeIdx));
    232       } else {
    233         Log.debug("Not legal to mutate code item " + codeItemIdx);
    234       }
    235       codeItemIdx++;
    236     }
    237   }
    238 
    239   private void registerMutator(CodeMutator mutator) {
    240     if (mutator.canBeTriggered()) {
    241       Log.debug("Registering mutator " + mutator.getClass().getSimpleName());
    242       mutators.add(mutator);
    243     }
    244     if (Options.loadMutations) {
    245       mutatorsLookupByClass.put(mutator.getClass(), mutator);
    246     }
    247   }
    248 
    249   /**
    250    * Associate ClassDefItem to a ClassDataItem and vice-versa.
    251    * This is so when we're associating method names with code items,
    252    * we can find the name of the class the method belongs to.
    253    */
    254   private void associateClassDefsAndClassData() {
    255     for (ClassDefItem classDefItem : rawDexFile.classDefs) {
    256       if (classDefItem.classDataOff.pointsToSomething()) {
    257         ClassDataItem classDataItem = (ClassDataItem)
    258             classDefItem.classDataOff.getPointedToItem();
    259         classDataItem.meta.classDefItem = classDefItem;
    260         classDefItem.meta.classDataItem = classDataItem;
    261       }
    262     }
    263   }
    264 
    265   /**
    266    * For each CodeItem, find the name of the method the item represents.
    267    * This is done to allow the filtering of mutating methods based on if
    268    * they have the name *_MUTATE, but also for debugging info.
    269    */
    270   private void associateCodeItemsWithMethodNames() {
    271     // Associate method names with codeItems.
    272     for (ClassDataItem classDataItem : rawDexFile.classDatas) {
    273 
    274       String className = "";
    275       if (classDataItem.meta.classDefItem != null) {
    276         int typeIdx = classDataItem.meta.classDefItem.classIdx;
    277         TypeIdItem typeIdItem = rawDexFile.typeIds.get(typeIdx);
    278         className = rawDexFile.stringDatas.get(typeIdItem.descriptorIdx).getString() + ".";
    279       }
    280 
    281       // Do direct methods...
    282       // Track the current method index with this value, since the encoding in
    283       // each EncodedMethod is the absolute index for the first EncodedMethod,
    284       // and then relative index for the rest...
    285       int methodIdx = 0;
    286       for (EncodedMethod method : classDataItem.directMethods) {
    287         methodIdx = associateMethod(method, methodIdx, className);
    288       }
    289       // Reset methodIdx for virtual methods...
    290       methodIdx = 0;
    291       for (EncodedMethod method : classDataItem.virtualMethods) {
    292         methodIdx = associateMethod(method, methodIdx, className);
    293       }
    294     }
    295   }
    296 
    297   /**
    298    * Associate the name of the provided method with its CodeItem, if it
    299    * has one.
    300    *
    301    * @param methodIdx The method index of the last EncodedMethod that was handled in this class.
    302    * @return The method index of the EncodedMethod that has just been handled in this class.
    303    */
    304   private int associateMethod(EncodedMethod method, int methodIdx, String className) {
    305     if (!method.codeOff.pointsToSomething()) {
    306       // This method doesn't have a code item, so we won't encounter it later.
    307       return methodIdx;
    308     }
    309 
    310     // First method index is an absolute index.
    311     // The rest are relative to the previous.
    312     // (so if methodIdx is initialised to 0, this single line works)
    313     methodIdx = methodIdx + method.methodIdxDiff;
    314 
    315     // Get the name.
    316     MethodIdItem methodIdItem = rawDexFile.methodIds.get(methodIdx);
    317     ProtoIdItem protoIdItem = rawDexFile.protoIds.get(methodIdItem.protoIdx);
    318     String shorty = rawDexFile.stringDatas.get(protoIdItem.shortyIdx).getString();
    319     String methodName = className
    320         + rawDexFile.stringDatas.get(methodIdItem.nameIdx).getString();
    321 
    322     // Get the codeItem.
    323     if (method.codeOff.getPointedToItem() instanceof CodeItem) {
    324       CodeItem codeItem = (CodeItem) method.codeOff.getPointedToItem();
    325       codeItem.meta.methodName = methodName;
    326       codeItem.meta.shorty = shorty;
    327       codeItem.meta.isStatic = method.isStatic();
    328     } else {
    329       Log.errorAndQuit("You've got an EncodedMethod that points to an Offsettable"
    330           + " that does not contain a CodeItem");
    331     }
    332 
    333     return methodIdx;
    334   }
    335 
    336   /**
    337    * Determine, based on the current options supplied to dexfuzz, as well as
    338    * its capabilities, if the provided CodeItem can be mutated.
    339    * @param codeItem The CodeItem we may wish to mutate.
    340    * @return If the CodeItem can be mutated.
    341    */
    342   private boolean legalToMutate(CodeItem codeItem) {
    343     if (!Options.mutateLimit) {
    344       Log.debug("Mutating everything.");
    345       return true;
    346     }
    347     if (codeItem.meta.methodName.endsWith("_MUTATE")) {
    348       Log.debug("Code item marked with _MUTATE.");
    349       return true;
    350     }
    351     Log.debug("Code item not marked with _MUTATE, but not mutating all code items.");
    352     return false;
    353   }
    354 
    355   private int getNumberOfMutationsToPerform() {
    356     // We want n mutations to be twice as likely as n+1 mutations.
    357     //
    358     // So if we have max 3,
    359     // then 0 has 8 chances ("tickets"),
    360     //      1 has 4 chances
    361     //      2 has 2 chances
    362     //  and 3 has 1 chance
    363 
    364     // Allocate the tickets
    365     // n mutations need (2^(n+1) - 1) tickets
    366     // e.g.
    367     // 3 mutations => 15 tickets
    368     // 4 mutations => 31 tickets
    369     int tickets = (2 << Options.methodMutations) - 1;
    370 
    371     // Pick the lucky ticket
    372     int luckyTicket = rng.nextInt(tickets);
    373 
    374     // The tickets are put into buckets with accordance with log-base-2.
    375     // have to make sure it's luckyTicket + 1, because log(0) is undefined
    376     // so:
    377     // log_2(1) => 0
    378     // log_2(2) => 1
    379     // log_2(3) => 1
    380     // log_2(4) => 2
    381     // log_2(5) => 2
    382     // log_2(6) => 2
    383     // log_2(7) => 2
    384     // log_2(8) => 3
    385     // ...
    386     // so to make the highest mutation value the rarest,
    387     //   subtract log_2(luckyTicket+1) from the maximum number
    388     // log2(x) <=> 31 - Integer.numberOfLeadingZeros(x)
    389     int luckyMutation = Options.methodMutations
    390         - (31 - Integer.numberOfLeadingZeros(luckyTicket + 1));
    391 
    392     return luckyMutation;
    393   }
    394 
    395   /**
    396    * Returns true if we completely failed to mutate this method's mutatable code after
    397    * attempting to.
    398    */
    399   private boolean mutateAMutatableCode(MutatableCode mutatableCode) {
    400     int mutations = getNumberOfMutationsToPerform();
    401 
    402     Log.info("Attempting " + mutations + " mutations for method " + mutatableCode.name);
    403 
    404     int mutationsApplied = 0;
    405 
    406     int maximumMutationAttempts = Options.methodMutations * MAXIMUM_MUTATION_ATTEMPT_FACTOR;
    407     int mutationAttempts = 0;
    408     boolean hadToBail = false;
    409 
    410     while (mutationsApplied < mutations) {
    411       int mutatorIdx = rng.nextInt(mutators.size());
    412       CodeMutator mutator = mutators.get(mutatorIdx);
    413       Log.info("Running mutator " + mutator.getClass().getSimpleName());
    414       if (mutator.attemptToMutate(mutatableCode)) {
    415         mutationsApplied++;
    416       }
    417       mutationAttempts++;
    418       if (mutationAttempts > maximumMutationAttempts) {
    419         Log.info("Bailing out on mutation for this method, tried too many times...");
    420         hadToBail = true;
    421         break;
    422       }
    423     }
    424 
    425     // If any of them actually mutated it, excellent!
    426     if (mutationsApplied > 0) {
    427       Log.info("Method was mutated.");
    428       mutatedCodes.add(mutatableCode);
    429     } else {
    430       Log.info("Method was not mutated.");
    431     }
    432 
    433     return ((mutationsApplied == 0) && hadToBail);
    434   }
    435 
    436   /**
    437    * Go through each mutatable method in turn, and attempt to mutate it.
    438    * Afterwards, call updateRawDexFile() to apply the results of mutation to the
    439    * original code.
    440    */
    441   public void mutateTheProgram() {
    442     if (Options.loadMutations) {
    443       applyMutationsFromList();
    444       return;
    445     }
    446 
    447     // Typically, this is 2 to 10...
    448     int methodsToMutate = Options.minMethods
    449         + rng.nextInt((Options.maxMethods - Options.minMethods) + 1);
    450 
    451     // Check we aren't trying to mutate more methods than we have.
    452     if (methodsToMutate > mutatableCodes.size()) {
    453       methodsToMutate = mutatableCodes.size();
    454     }
    455 
    456     // Check if we're going to end up mutating all the methods.
    457     if (methodsToMutate == mutatableCodes.size()) {
    458       // Just do them all in order.
    459       Log.info("Mutating all possible methods.");
    460       for (MutatableCode mutatableCode : mutatableCodes) {
    461         if (mutatableCode == null) {
    462           Log.errorAndQuit("Why do you have a null MutatableCode?");
    463         }
    464         mutateAMutatableCode(mutatableCode);
    465       }
    466       Log.info("Finished mutating all possible methods.");
    467     } else {
    468       // Pick them at random.
    469       Log.info("Randomly selecting " + methodsToMutate + " methods to mutate.");
    470       while (mutatedCodes.size() < methodsToMutate) {
    471         int randomMethodIdx = rng.nextInt(mutatableCodes.size());
    472         MutatableCode mutatableCode = mutatableCodes.get(randomMethodIdx);
    473         if (mutatableCode == null) {
    474           Log.errorAndQuit("Why do you have a null MutatableCode?");
    475         }
    476         if (!mutatedCodes.contains(mutatableCode)) {
    477           boolean completelyFailedToMutate = mutateAMutatableCode(mutatableCode);
    478           if (completelyFailedToMutate) {
    479             methodsToMutate--;
    480           }
    481         }
    482       }
    483       Log.info("Finished mutating the methods.");
    484     }
    485 
    486     listener.handleMutationStats(mutationStats.getStatsString());
    487 
    488     if (Options.dumpMutations) {
    489       writeMutationsToDisk(Options.dumpMutationsFile);
    490     }
    491   }
    492 
    493   private void writeMutationsToDisk(String fileName) {
    494     Log.debug("Writing mutations to disk.");
    495     try {
    496       BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
    497       for (Mutation mutation : mutations) {
    498         MutationSerializer.writeMutation(writer, mutation);
    499       }
    500       writer.close();
    501     } catch (IOException e) {
    502       Log.errorAndQuit("IOException while writing mutations to disk...");
    503     }
    504   }
    505 
    506   private void loadMutationsFromDisk(String fileName) {
    507     Log.debug("Loading mutations from disk.");
    508     try {
    509       BufferedReader reader = new BufferedReader(new FileReader(fileName));
    510       while (reader.ready()) {
    511         Mutation mutation = MutationSerializer.readMutation(reader);
    512         mutations.add(mutation);
    513       }
    514       reader.close();
    515     } catch (IOException e) {
    516       Log.errorAndQuit("IOException while loading mutations from disk...");
    517     }
    518   }
    519 
    520   private void applyMutationsFromList() {
    521     Log.info("Applying preloaded list of mutations...");
    522     for (Mutation mutation : mutations) {
    523       // Repopulate the MutatableCode field from the recorded index into the Program's list.
    524       mutation.mutatableCode = mutatableCodes.get(mutation.mutatableCodeIdx);
    525 
    526       // Get the right mutator.
    527       CodeMutator mutator = mutatorsLookupByClass.get(mutation.mutatorClass);
    528 
    529       // Apply the mutation.
    530       mutator.forceMutate(mutation);
    531 
    532       // Add this mutatable code to the list of mutated codes, if we haven't already.
    533       if (!mutatedCodes.contains(mutation.mutatableCode)) {
    534         mutatedCodes.add(mutation.mutatableCode);
    535       }
    536     }
    537     Log.info("...finished applying preloaded list of mutations.");
    538   }
    539 
    540   public List<Mutation> getMutations() {
    541     return mutations;
    542   }
    543 
    544   /**
    545    * Updates any CodeItems that need to be updated after mutation.
    546    */
    547   public boolean updateRawDexFile() {
    548     boolean anythingMutated = !(mutatedCodes.isEmpty());
    549     for (MutatableCode mutatedCode : mutatedCodes) {
    550       translator.mutatableCodeToCodeItem(rawDexFile.codeItems
    551           .get(mutatedCode.codeItemIdx), mutatedCode);
    552     }
    553     mutatedCodes.clear();
    554     return anythingMutated;
    555   }
    556 
    557   public void writeRawDexFile(DexRandomAccessFile file) throws IOException {
    558     rawDexFile.write(file);
    559   }
    560 
    561   public void updateRawDexFileHeader(DexRandomAccessFile file) throws IOException {
    562     rawDexFile.updateHeader(file);
    563   }
    564 
    565   /**
    566    * Used by the CodeMutators to determine legal index values.
    567    */
    568   public int getTotalPoolIndicesByKind(PoolIndexKind poolIndexKind) {
    569     switch (poolIndexKind) {
    570       case Type:
    571         return rawDexFile.typeIds.size();
    572       case Field:
    573         return rawDexFile.fieldIds.size();
    574       case String:
    575         return rawDexFile.stringIds.size();
    576       case Method:
    577         return rawDexFile.methodIds.size();
    578       case Invalid:
    579         return 0;
    580       default:
    581     }
    582     return 0;
    583   }
    584 
    585   /**
    586    * Used by the CodeMutators to lookup and/or create Ids.
    587    */
    588   public IdCreator getNewItemCreator() {
    589     return idCreator;
    590   }
    591 
    592   /**
    593    * Used by FieldFlagChanger, to find an EncodedField for a specified field in an insn,
    594    * if that field is actually defined in this DEX file. If not, null is returned.
    595    */
    596   public EncodedField getEncodedField(int fieldIdx) {
    597     if (fieldIdx >= rawDexFile.fieldIds.size()) {
    598       Log.debug(String.format("Field idx 0x%x specified is not defined in this DEX file.",
    599           fieldIdx));
    600       return null;
    601     }
    602     FieldIdItem fieldId = rawDexFile.fieldIds.get(fieldIdx);
    603 
    604     for (ClassDefItem classDef : rawDexFile.classDefs) {
    605       if (classDef.classIdx == fieldId.classIdx) {
    606         ClassDataItem classData = classDef.meta.classDataItem;
    607         return classData.getEncodedFieldWithIndex(fieldIdx);
    608       }
    609     }
    610 
    611     Log.debug(String.format("Field idx 0x%x specified is not defined in this DEX file.",
    612         fieldIdx));
    613     return null;
    614   }
    615 
    616   /**
    617    * Used to convert the type index into string format.
    618    * @param typeIdx
    619    * @return string format of type index.
    620    */
    621   public String getTypeString(int typeIdx) {
    622     TypeIdItem typeIdItem = rawDexFile.typeIds.get(typeIdx);
    623     return rawDexFile.stringDatas.get(typeIdItem.descriptorIdx).getString();
    624   }
    625 
    626   /**
    627    * Used to convert the method index into string format.
    628    * @param methodIdx
    629    * @return string format of method index.
    630    */
    631   public String getMethodString(int methodIdx) {
    632     MethodIdItem methodIdItem = rawDexFile.methodIds.get(methodIdx);
    633     return rawDexFile.stringDatas.get(methodIdItem.nameIdx).getString();
    634   }
    635 
    636   /**
    637    * Used to convert methodID to string format of method proto.
    638    * @param methodIdx
    639    * @return string format of shorty.
    640    */
    641   public String getMethodProto(int methodIdx) {
    642     MethodIdItem methodIdItem = rawDexFile.methodIds.get(methodIdx);
    643     ProtoIdItem protoIdItem = rawDexFile.protoIds.get(methodIdItem.protoIdx);
    644 
    645     if (!protoIdItem.parametersOff.pointsToSomething()) {
    646       return "()" + getTypeString(protoIdItem.returnTypeIdx);
    647     }
    648 
    649     TypeList typeList = (TypeList) protoIdItem.parametersOff.getPointedToItem();
    650     String typeItem = "(";
    651     for (int i= 0; i < typeList.size; i++) {
    652       typeItem = typeItem + typeList.list[i];
    653     }
    654     return typeItem + ")" + getTypeString(protoIdItem.returnTypeIdx);
    655   }
    656 }