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    */
Program(RawDexFile rawDexFile, List<Mutation> previousMutations, BaseListener listener)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 
registerMutator(CodeMutator mutator)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    */
associateClassDefsAndClassData()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    */
associateCodeItemsWithMethodNames()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    */
associateMethod(EncodedMethod method, int methodIdx, String className)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    */
legalToMutate(CodeItem codeItem)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 
getNumberOfMutationsToPerform()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    */
mutateAMutatableCode(MutatableCode mutatableCode)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    */
mutateTheProgram()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 
writeMutationsToDisk(String fileName)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 
loadMutationsFromDisk(String fileName)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 
applyMutationsFromList()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 
getMutations()540   public List<Mutation> getMutations() {
541     return mutations;
542   }
543 
544   /**
545    * Updates any CodeItems that need to be updated after mutation.
546    */
updateRawDexFile()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 
writeRawDexFile(DexRandomAccessFile file)557   public void writeRawDexFile(DexRandomAccessFile file) throws IOException {
558     rawDexFile.write(file);
559   }
560 
updateRawDexFileHeader(DexRandomAccessFile file)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    */
getTotalPoolIndicesByKind(PoolIndexKind poolIndexKind)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    */
getNewItemCreator()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    */
getEncodedField(int fieldIdx)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    */
getTypeString(int typeIdx)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    */
getMethodString(int methodIdx)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    */
getMethodProto(int methodIdx)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 }