MethodDefinition.java revision b2e1e2067d5179b0ec38c76a5191909f5a7dc1cc
1/* 2 * [The "BSD licence"] 3 * Copyright (c) 2009 Ben Gruver 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.Adaptors.Format.*; 32import org.jf.baksmali.baksmali; 33import org.jf.dexlib.*; 34import org.jf.dexlib.Debug.DebugInstructionIterator; 35import org.jf.dexlib.Code.Instruction; 36import org.jf.dexlib.Code.Opcode; 37import org.jf.dexlib.Code.OffsetInstruction; 38import org.jf.dexlib.Util.AccessFlags; 39import org.antlr.stringtemplate.StringTemplateGroup; 40import org.antlr.stringtemplate.StringTemplate; 41import org.jf.dexlib.Util.SparseIntArray; 42 43import java.util.*; 44 45public class MethodDefinition { 46 private final StringTemplateGroup stg; 47 private final ClassDataItem.EncodedMethod encodedMethod; 48 49 private final LabelCache labelCache = new LabelCache(); 50 51 private final SparseIntArray packedSwitchMap; 52 private final SparseIntArray sparseSwitchMap; 53 private final SparseIntArray instructionMap; 54 55 public MethodDefinition(StringTemplateGroup stg, ClassDataItem.EncodedMethod encodedMethod) { 56 this.stg = stg; 57 this.encodedMethod = encodedMethod; 58 59 //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh. 60 61 if (encodedMethod.codeItem != null) { 62 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 63 64 packedSwitchMap = new SparseIntArray(1); 65 sparseSwitchMap = new SparseIntArray(1); 66 instructionMap = new SparseIntArray(instructions.length); 67 68 int currentCodeAddress = 0; 69 for (int i=0; i<instructions.length; i++) { 70 Instruction instruction = instructions[i]; 71 if (instruction.opcode == Opcode.PACKED_SWITCH) { 72 packedSwitchMap.append( 73 currentCodeAddress + ((OffsetInstruction)instruction).getTargetAddressOffset(), 74 currentCodeAddress); 75 } else if (instruction.opcode == Opcode.SPARSE_SWITCH) { 76 sparseSwitchMap.append( 77 currentCodeAddress + ((OffsetInstruction)instruction).getTargetAddressOffset(), 78 currentCodeAddress); 79 } 80 instructionMap.append(currentCodeAddress, i); 81 currentCodeAddress += instruction.getSize(currentCodeAddress); 82 } 83 } else { 84 packedSwitchMap = null; 85 sparseSwitchMap = null; 86 instructionMap = null; 87 } 88 } 89 90 public StringTemplate createTemplate(AnnotationSetItem annotationSet, 91 AnnotationSetRefList parameterAnnotations) { 92 93 CodeItem codeItem = encodedMethod.codeItem; 94 95 StringTemplate template = stg.getInstanceOf("method"); 96 97 template.setAttribute("AccessFlags", getAccessFlags(encodedMethod)); 98 template.setAttribute("MethodName", encodedMethod.method.getMethodName().getStringValue()); 99 template.setAttribute("Prototype", encodedMethod.method.getPrototype().getPrototypeString()); 100 template.setAttribute("HasCode", codeItem != null); 101 template.setAttribute("RegistersDirective", baksmali.useLocalsDirective?".locals":".registers"); 102 template.setAttribute("RegisterCount", codeItem==null?"0":Integer.toString(getRegisterCount(encodedMethod))); 103 template.setAttribute("Parameters", getParameters(stg, codeItem, parameterAnnotations)); 104 template.setAttribute("Annotations", getAnnotations(stg, annotationSet)); 105 template.setAttribute("MethodItems", getMethodItems()); 106 107 return template; 108 } 109 110 private static int getRegisterCount(ClassDataItem.EncodedMethod encodedMethod) 111 { 112 int totalRegisters = encodedMethod.codeItem.getRegisterCount(); 113 if (baksmali.useLocalsDirective) { 114 int parameterRegisters = encodedMethod.method.getPrototype().getParameterRegisterCount(); 115 if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) { 116 parameterRegisters++; 117 } 118 return totalRegisters - parameterRegisters; 119 } 120 return totalRegisters; 121 } 122 123 private static List<String> getAccessFlags(ClassDataItem.EncodedMethod encodedMethod) { 124 List<String> accessFlags = new ArrayList<String>(); 125 126 for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.accessFlags)) { 127 accessFlags.add(accessFlag.toString()); 128 } 129 130 return accessFlags; 131 } 132 133 private static List<StringTemplate> getParameters(StringTemplateGroup stg, CodeItem codeItem, 134 AnnotationSetRefList parameterAnnotations) { 135 DebugInfoItem debugInfoItem = null; 136 if (baksmali.outputDebugInfo && codeItem != null) { 137 debugInfoItem = codeItem.getDebugInfo(); 138 } 139 140 int parameterCount = 0; 141 142 List<AnnotationSetItem> annotations = new ArrayList<AnnotationSetItem>(); 143 if (parameterAnnotations != null) { 144 AnnotationSetItem[] _annotations = parameterAnnotations.getAnnotationSets(); 145 if (_annotations != null) { 146 annotations.addAll(Arrays.asList(_annotations)); 147 } 148 149 parameterCount = annotations.size(); 150 } 151 152 List<String> parameterNames = new ArrayList<String>(); 153 if (debugInfoItem != null) { 154 StringIdItem[] _parameterNames = debugInfoItem.getParameterNames(); 155 if (_parameterNames != null) { 156 for (StringIdItem parameterName: _parameterNames) { 157 parameterNames.add(parameterName==null?null:parameterName.getStringValue()); 158 } 159 } 160 161 if (parameterCount < parameterNames.size()) { 162 parameterCount = parameterNames.size(); 163 } 164 } 165 166 List<StringTemplate> parameters = new ArrayList<StringTemplate>(); 167 for (int i=0; i<parameterCount; i++) { 168 AnnotationSetItem annotationSet = null; 169 if (i < annotations.size()) { 170 annotationSet = annotations.get(i); 171 } 172 173 String parameterName = null; 174 if (i < parameterNames.size()) { 175 parameterName = parameterNames.get(i); 176 } 177 178 parameters.add(ParameterAdaptor.createTemplate(stg, parameterName, annotationSet)); 179 } 180 181 return parameters; 182 } 183 184 public LabelCache getLabelCache() { 185 return labelCache; 186 } 187 188 public int getPackedSwitchBaseAddress(int packedSwitchDataAddress) { 189 int packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress, -1); 190 191 if (packedSwitchBaseAddress == -1) { 192 throw new RuntimeException("Could not find the packed switch statement corresponding to the packed " + 193 "switch data at address " + packedSwitchDataAddress); 194 } 195 196 return packedSwitchBaseAddress; 197 } 198 199 public int getSparseSwitchBaseAddress(int sparseSwitchDataAddress) { 200 int sparseSwitchBaseAddress = this.sparseSwitchMap.get(sparseSwitchDataAddress, -1); 201 202 if (sparseSwitchBaseAddress == -1) { 203 throw new RuntimeException("Could not find the sparse switch statement corresponding to the sparse " + 204 "switch data at address " + sparseSwitchDataAddress); 205 } 206 207 return sparseSwitchBaseAddress; 208 } 209 210 private static List<StringTemplate> getAnnotations(StringTemplateGroup stg, AnnotationSetItem annotationSet) { 211 if (annotationSet == null) { 212 return null; 213 } 214 215 List<StringTemplate> annotationAdaptors = new ArrayList<StringTemplate>(); 216 217 for (AnnotationItem annotationItem: annotationSet.getAnnotations()) { 218 annotationAdaptors.add(AnnotationAdaptor.createTemplate(stg, annotationItem)); 219 } 220 return annotationAdaptors; 221 } 222 223 private List<MethodItem> getMethodItems() { 224 List<MethodItem> methodItems = new ArrayList<MethodItem>(); 225 226 if (encodedMethod.codeItem == null) { 227 return methodItems; 228 } 229 230 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 231 232 int currentCodeAddress = 0; 233 for (int i=0; i<instructions.length; i++) { 234 Instruction instruction = instructions[i]; 235 236 methodItems.add(InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 237 encodedMethod.codeItem, currentCodeAddress, stg, instruction)); 238 239 if (i != instructions.length - 1) { 240 methodItems.add(new BlankMethodItem(stg, currentCodeAddress)); 241 } 242 243 currentCodeAddress += instruction.getSize(currentCodeAddress); 244 } 245 246 addTries(methodItems); 247 addDebugInfo(methodItems); 248 249 if (baksmali.useSequentialLabels) { 250 setLabelSequentialNumbers(); 251 } 252 253 254 for (LabelMethodItem labelMethodItem: labelCache.getLabels()) { 255 if (labelMethodItem.isCommentedOut()) { 256 methodItems.add(new CommentedOutMethodItem(stg, labelMethodItem)); 257 } else { 258 methodItems.add(labelMethodItem); 259 } 260 } 261 262 Collections.sort(methodItems); 263 264 return methodItems; 265 } 266 267 private void addTries(List<MethodItem> methodItems) { 268 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) { 269 return; 270 } 271 272 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 273 274 for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) { 275 int startAddress = tryItem.getStartCodeAddress(); 276 int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength(); 277 278 /** 279 * The end address points to the address immediately after the end of the last 280 * instruction that the try block covers. We want the .catch directive and end_try 281 * label to be associated with the last covered instruction, so we need to get 282 * the address for that instruction 283 */ 284 285 int index = instructionMap.get(endAddress, -1); 286 int lastInstructionAddress; 287 288 /** 289 * If we couldn't find the index, then the try block probably extends to the last instruction in the 290 * method, and so endAddress would be the address immediately after the end of the last instruction. 291 * Check to make sure this is the case, if not, throw an exception. 292 */ 293 if (index == -1) { 294 Instruction lastInstruction = instructions[instructions.length - 1]; 295 lastInstructionAddress = instructionMap.keyAt(instructionMap.size() - 1); 296 297 if (endAddress != lastInstructionAddress + lastInstruction.getSize(lastInstructionAddress)) { 298 throw new RuntimeException("Invalid code offset " + endAddress + " for the try block end address"); 299 } 300 } else { 301 if (index == 0) { 302 throw new RuntimeException("Unexpected instruction index"); 303 } 304 Instruction lastInstruction = instructions[index - 1]; 305 306 if (lastInstruction.getFormat().variableSizeFormat) { 307 throw new RuntimeException("This try block unexpectedly ends on a switch/array data block."); 308 } 309 310 //getSize for non-variable size formats should return the same size regardless of code address, so just 311 //use a dummy address of "0" 312 lastInstructionAddress = endAddress - lastInstruction.getSize(0); 313 } 314 315 //add the catch all handler if it exists 316 int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress(); 317 if (catchAllAddress != -1) { 318 CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, stg, null, 319 startAddress, endAddress, catchAllAddress); 320 methodItems.add(catchAllMethodItem); 321 } 322 323 //add the rest of the handlers 324 for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) { 325 //use the address from the last covered instruction 326 CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, stg, 327 handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress()); 328 methodItems.add(catchMethodItem); 329 } 330 } 331 } 332 333 private void addDebugInfo(final List<MethodItem> methodItems) { 334 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) { 335 return; 336 } 337 338 final CodeItem codeItem = encodedMethod.codeItem; 339 DebugInfoItem debugInfoItem = codeItem.getDebugInfo(); 340 341 DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(), 342 new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() { 343 @Override 344 public void ProcessStartLocal(int codeAddress, int length, int registerNum, StringIdItem name, 345 TypeIdItem type) { 346 methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "StartLocal", 347 -1, registerNum, name, type, null)); 348 } 349 350 @Override 351 public void ProcessStartLocalExtended(int codeAddress, int length, int registerNum, 352 StringIdItem name, TypeIdItem type, 353 StringIdItem signature) { 354 methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "StartLocal", 355 -1, registerNum, name, type, signature)); 356 } 357 358 @Override 359 public void ProcessEndLocal(int codeAddress, int length, int registerNum, StringIdItem name, 360 TypeIdItem type, StringIdItem signature) { 361 methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "EndLocal", -1, 362 registerNum, name, type, signature)); 363 } 364 365 @Override 366 public void ProcessRestartLocal(int codeAddress, int length, int registerNum, StringIdItem name, 367 TypeIdItem type, StringIdItem signature) { 368 methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "RestartLocal", -1, 369 registerNum, name, type, signature)); 370 } 371 372 @Override 373 public void ProcessSetPrologueEnd(int codeAddress) { 374 methodItems.add(new DebugMethodItem(codeAddress, stg, "EndPrologue", -4)); 375 } 376 377 @Override 378 public void ProcessSetEpilogueBegin(int codeAddress) { 379 methodItems.add(new DebugMethodItem(codeAddress, stg, "StartEpilogue", -4)); 380 } 381 382 @Override 383 public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) { 384 methodItems.add(new DebugMethodItem(codeAddress, stg, "SetFile", -3) { 385 @Override 386 protected void setAttributes(StringTemplate template) { 387 template.setAttribute("FileName", name.getStringValue()); 388 } 389 }); 390 } 391 392 @Override 393 public void ProcessLineEmit(int codeAddress, final int line) { 394 methodItems.add(new DebugMethodItem(codeAddress, stg, "Line", -2) { 395 @Override 396 protected void setAttributes(StringTemplate template) { 397 template.setAttribute("Line", line); 398 } 399 }); 400 } 401 }); 402 } 403 404 private void setLabelSequentialNumbers() { 405 HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>(); 406 ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels()); 407 408 //sort the labels by their location in the method 409 Collections.sort(sortedLabels); 410 411 for (LabelMethodItem labelMethodItem: sortedLabels) { 412 Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix()); 413 if (labelSequence == null) { 414 labelSequence = 0; 415 } 416 labelMethodItem.setLabelSequence(labelSequence); 417 nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1); 418 } 419 } 420 421 public static class LabelCache { 422 protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>(); 423 424 public LabelCache() { 425 } 426 427 public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) { 428 LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem); 429 if (internedLabelMethodItem != null) { 430 if (!labelMethodItem.isCommentedOut()) { 431 internedLabelMethodItem.setUncommented(); 432 } 433 return internedLabelMethodItem; 434 } 435 labels.put(labelMethodItem, labelMethodItem); 436 return labelMethodItem; 437 } 438 439 440 public Collection<LabelMethodItem> getLabels() { 441 return labels.values(); 442 } 443 } 444} 445