1/* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu) 6 * 7 * This program is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License as published by the Free 9 * Software Foundation; either version 2 of the License, or (at your option) 10 * any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 * more details. 16 * 17 * You should have received a copy of the GNU General Public License along 18 * with this program; if not, write to the Free Software Foundation, Inc., 19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21package proguard.preverify; 22 23import proguard.classfile.*; 24import proguard.classfile.attribute.*; 25import proguard.classfile.attribute.visitor.*; 26import proguard.classfile.editor.CodeAttributeComposer; 27import proguard.classfile.instruction.*; 28import proguard.classfile.instruction.visitor.InstructionVisitor; 29import proguard.classfile.util.SimplifiedVisitor; 30import proguard.classfile.visitor.*; 31import proguard.optimize.peephole.BranchTargetFinder; 32 33/** 34 * This AttributeVisitor inlines local subroutines (jsr/ret) in the code 35 * attributes that it visits. 36 * 37 * @author Eric Lafortune 38 */ 39public class CodeSubroutineInliner 40extends SimplifiedVisitor 41implements AttributeVisitor, 42 InstructionVisitor, 43 ExceptionInfoVisitor 44{ 45 //* 46 private static final boolean DEBUG = false; 47 /*/ 48 private static boolean DEBUG = System.getProperty("csi") != null; 49 //*/ 50 51 52 private final BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); 53 private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(true, true); 54 55 private ExceptionInfoVisitor subroutineExceptionInliner = this; 56 private int clipStart = 0; 57 private int clipEnd = Integer.MAX_VALUE; 58 59 60 // Implementations for AttributeVisitor. 61 62 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 63 64 65 public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) 66 { 67// DEBUG = 68// clazz.getName().equals("abc/Def") && 69// method.getName(clazz).equals("abc"); 70// CodeAttributeComposer.DEBUG = DEBUG; 71 72 // TODO: Remove this when the subroutine inliner has stabilized. 73 // Catch any unexpected exceptions from the actual visiting method. 74 try 75 { 76 // Process the code. 77 visitCodeAttribute0(clazz, method, codeAttribute); 78 } 79 catch (RuntimeException ex) 80 { 81 System.err.println("Unexpected error while inlining subroutines:"); 82 System.err.println(" Class = ["+clazz.getName()+"]"); 83 System.err.println(" Method = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]"); 84 System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); 85 86 if (DEBUG) 87 { 88 method.accept(clazz, new ClassPrinter()); 89 } 90 91 throw ex; 92 } 93 } 94 95 96 public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) 97 { 98 branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute); 99 100 // Don't bother if there aren't any subroutines anyway. 101 if (!branchTargetFinder.containsSubroutines()) 102 { 103 return; 104 } 105 106 if (DEBUG) 107 { 108 System.out.println("SubroutineInliner: processing ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]"); 109 } 110 111 // Append the body of the code. 112 codeAttributeComposer.reset(); 113 codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); 114 115 // Copy the non-subroutine instructions. 116 int offset = 0; 117 while (offset < codeAttribute.u4codeLength) 118 { 119 Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); 120 int instructionLength = instruction.length(offset); 121 122 // Is this returning subroutine? 123 if (branchTargetFinder.isSubroutine(offset) && 124 branchTargetFinder.isSubroutineReturning(offset)) 125 { 126 // Skip the subroutine. 127 if (DEBUG) 128 { 129 System.out.println(" Skipping original subroutine instruction "+instruction.toString(offset)); 130 } 131 132 // Append a label at this offset instead. 133 codeAttributeComposer.appendLabel(offset); 134 } 135 else 136 { 137 // Copy the instruction, inlining any subroutine call recursively. 138 instruction.accept(clazz, method, codeAttribute, offset, this); 139 } 140 141 offset += instructionLength; 142 } 143 144 // Copy the exceptions. Note that exceptions with empty try blocks 145 // are automatically removed. 146 codeAttribute.exceptionsAccept(clazz, 147 method, 148 subroutineExceptionInliner); 149 150 if (DEBUG) 151 { 152 System.out.println(" Appending label after code at ["+offset+"]"); 153 } 154 155 // Append a label just after the code. 156 codeAttributeComposer.appendLabel(codeAttribute.u4codeLength); 157 158 // End and update the code attribute. 159 codeAttributeComposer.endCodeFragment(); 160 codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute); 161 } 162 163 164 /** 165 * Appends the specified subroutine. 166 */ 167 private void inlineSubroutine(Clazz clazz, 168 Method method, 169 CodeAttribute codeAttribute, 170 int subroutineInvocationOffset, 171 int subroutineStart) 172 { 173 int subroutineEnd = branchTargetFinder.subroutineEnd(subroutineStart); 174 175 if (DEBUG) 176 { 177 System.out.println(" Inlining subroutine ["+subroutineStart+" -> "+subroutineEnd+"] at ["+subroutineInvocationOffset+"]"); 178 } 179 180 // Don't go inlining exceptions that are already applicable to this 181 // subroutine invocation. 182 ExceptionInfoVisitor oldSubroutineExceptionInliner = subroutineExceptionInliner; 183 int oldClipStart = clipStart; 184 int oldClipEnd = clipEnd; 185 186 subroutineExceptionInliner = 187 new ExceptionExcludedOffsetFilter(subroutineInvocationOffset, 188 subroutineExceptionInliner); 189 clipStart = subroutineStart; 190 clipEnd = subroutineEnd; 191 192 codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); 193 194 // Copy the subroutine instructions, inlining any subroutine calls 195 // recursively. 196 codeAttribute.instructionsAccept(clazz, 197 method, 198 subroutineStart, 199 subroutineEnd, 200 this); 201 202 if (DEBUG) 203 { 204 System.out.println(" Appending label after inlined subroutine at ["+subroutineEnd+"]"); 205 } 206 207 // Append a label just after the code. 208 codeAttributeComposer.appendLabel(subroutineEnd); 209 210 // Inline the subroutine exceptions. 211 codeAttribute.exceptionsAccept(clazz, 212 method, 213 subroutineStart, 214 subroutineEnd, 215 subroutineExceptionInliner); 216 217 // We can again inline exceptions that are applicable to this 218 // subroutine invocation. 219 subroutineExceptionInliner = oldSubroutineExceptionInliner; 220 clipStart = oldClipStart; 221 clipEnd = oldClipEnd; 222 223 codeAttributeComposer.endCodeFragment(); 224 } 225 226 227 // Implementations for InstructionVisitor. 228 229 public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) 230 { 231 // Append the instruction. 232 codeAttributeComposer.appendInstruction(offset, instruction); 233 } 234 235 236 public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) 237 { 238 byte opcode = variableInstruction.opcode; 239 if (opcode == InstructionConstants.OP_RET) 240 { 241 // Is the return instruction the last instruction of the subroutine? 242 if (branchTargetFinder.subroutineEnd(offset) == offset + variableInstruction.length(offset)) 243 { 244 if (DEBUG) 245 { 246 System.out.println(" Replacing subroutine return at ["+offset+"] by a label"); 247 } 248 249 // Append a label at this offset instead of the subroutine return. 250 codeAttributeComposer.appendLabel(offset); 251 } 252 else 253 { 254 if (DEBUG) 255 { 256 System.out.println(" Replacing subroutine return at ["+offset+"] by a simple branch"); 257 } 258 259 // Replace the instruction by a branch. 260 Instruction replacementInstruction = 261 new BranchInstruction(InstructionConstants.OP_GOTO, 262 branchTargetFinder.subroutineEnd(offset) - offset); 263 264 codeAttributeComposer.appendInstruction(offset, replacementInstruction); 265 } 266 } 267 else if (branchTargetFinder.isSubroutineStart(offset)) 268 { 269 if (DEBUG) 270 { 271 System.out.println(" Replacing first subroutine instruction at ["+offset+"] by a label"); 272 } 273 274 // Append a label at this offset instead of saving the subroutine 275 // return address. 276 codeAttributeComposer.appendLabel(offset); 277 } 278 else 279 { 280 // Append the instruction. 281 codeAttributeComposer.appendInstruction(offset, variableInstruction); 282 } 283 } 284 285 286 public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) 287 { 288 byte opcode = branchInstruction.opcode; 289 if (opcode == InstructionConstants.OP_JSR || 290 opcode == InstructionConstants.OP_JSR_W) 291 { 292 int branchOffset = branchInstruction.branchOffset; 293 int branchTarget = offset + branchOffset; 294 295 // Is the subroutine ever returning? 296 if (branchTargetFinder.isSubroutineReturning(branchTarget)) 297 { 298 // Append a label at this offset instead of the subroutine invocation. 299 codeAttributeComposer.appendLabel(offset); 300 301 // Inline the invoked subroutine. 302 inlineSubroutine(clazz, 303 method, 304 codeAttribute, 305 offset, 306 branchTarget); 307 } 308 else 309 { 310 if (DEBUG) 311 { 312 System.out.println("Replacing subroutine invocation at ["+offset+"] by a simple branch"); 313 } 314 315 // Replace the subroutine invocation by a simple branch. 316 Instruction replacementInstruction = 317 new BranchInstruction(InstructionConstants.OP_GOTO, 318 branchOffset); 319 320 codeAttributeComposer.appendInstruction(offset, replacementInstruction); 321 } 322 } 323 else 324 { 325 // Append the instruction. 326 codeAttributeComposer.appendInstruction(offset, branchInstruction); 327 } 328 } 329 330 331 // Implementations for ExceptionInfoVisitor. 332 333 public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) 334 { 335 int startPC = Math.max(exceptionInfo.u2startPC, clipStart); 336 int endPC = Math.min(exceptionInfo.u2endPC, clipEnd); 337 int handlerPC = exceptionInfo.u2handlerPC; 338 int catchType = exceptionInfo.u2catchType; 339 340 // Exclude any subroutine invocations that jump out of the try block, 341 // by adding a try block before (and later on, after) each invocation. 342 for (int offset = startPC; offset < endPC; offset++) 343 { 344 if (branchTargetFinder.isSubroutineInvocation(offset)) 345 { 346 Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); 347 int instructionLength = instruction.length(offset); 348 349 // Is it a subroutine invocation? 350 if (!exceptionInfo.isApplicable(offset + ((BranchInstruction)instruction).branchOffset)) 351 { 352 if (DEBUG) 353 { 354 System.out.println(" Appending extra exception ["+startPC+" -> "+offset+"] -> "+handlerPC); 355 } 356 357 // Append a try block that ends before the subroutine invocation. 358 codeAttributeComposer.appendException(new ExceptionInfo(startPC, 359 offset, 360 handlerPC, 361 catchType)); 362 363 // The next try block will start after the subroutine invocation. 364 startPC = offset + instructionLength; 365 } 366 } 367 } 368 369 if (DEBUG) 370 { 371 if (startPC == exceptionInfo.u2startPC && 372 endPC == exceptionInfo.u2endPC) 373 { 374 System.out.println(" Appending exception ["+startPC+" -> "+endPC+"] -> "+handlerPC); 375 } 376 else 377 { 378 System.out.println(" Appending clipped exception ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+"] ~> ["+startPC+" -> "+endPC+"] -> "+handlerPC); 379 } 380 } 381 382 // Append the exception. Note that exceptions with empty try blocks 383 // are automatically ignored. 384 codeAttributeComposer.appendException(new ExceptionInfo(startPC, 385 endPC, 386 handlerPC, 387 catchType)); 388 } 389} 390