1/* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2014 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 private final BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); 52 private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(true, true, true); 53 54 private ExceptionInfoVisitor subroutineExceptionInliner = this; 55 private int clipStart = 0; 56 private int clipEnd = Integer.MAX_VALUE; 57 58 59 // Implementations for AttributeVisitor. 60 61 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 62 63 64 public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) 65 { 66// DEBUG = 67// clazz.getName().equals("abc/Def") && 68// method.getName(clazz).equals("abc"); 69// CodeAttributeComposer.DEBUG = DEBUG; 70 71 // TODO: Remove this when the subroutine inliner has stabilized. 72 // Catch any unexpected exceptions from the actual visiting method. 73 try 74 { 75 // Process the code. 76 visitCodeAttribute0(clazz, method, codeAttribute); 77 } 78 catch (RuntimeException ex) 79 { 80 System.err.println("Unexpected error while inlining subroutines:"); 81 System.err.println(" Class = ["+clazz.getName()+"]"); 82 System.err.println(" Method = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]"); 83 System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); 84 85 if (DEBUG) 86 { 87 method.accept(clazz, new ClassPrinter()); 88 } 89 90 throw ex; 91 } 92 } 93 94 95 public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) 96 { 97 branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute); 98 99 // Don't bother if there aren't any subroutines anyway. 100 if (!branchTargetFinder.containsSubroutines()) 101 { 102 return; 103 } 104 105 if (DEBUG) 106 { 107 System.out.println("SubroutineInliner: processing ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]"); 108 } 109 110 // Append the body of the code. 111 codeAttributeComposer.reset(); 112 codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); 113 114 // Copy the non-subroutine instructions. 115 int offset = 0; 116 while (offset < codeAttribute.u4codeLength) 117 { 118 Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); 119 int instructionLength = instruction.length(offset); 120 121 // Is this a returning subroutine? 122 if (branchTargetFinder.isSubroutine(offset) && 123 branchTargetFinder.isSubroutineReturning(offset)) 124 { 125 // Skip the subroutine. 126 if (DEBUG) 127 { 128 System.out.println(" Skipping original subroutine instruction "+instruction.toString(offset)); 129 } 130 131 // Append a label at this offset instead. 132 codeAttributeComposer.appendLabel(offset); 133 } 134 else 135 { 136 // Copy the instruction, inlining any subroutine call recursively. 137 instruction.accept(clazz, method, codeAttribute, offset, this); 138 } 139 140 offset += instructionLength; 141 } 142 143 // Copy the exceptions. Note that exceptions with empty try blocks 144 // are automatically removed. 145 codeAttribute.exceptionsAccept(clazz, 146 method, 147 subroutineExceptionInliner); 148 149 if (DEBUG) 150 { 151 System.out.println(" Appending label after code at ["+offset+"]"); 152 } 153 154 // Append a label just after the code. 155 codeAttributeComposer.appendLabel(codeAttribute.u4codeLength); 156 157 // End and update the code attribute. 158 codeAttributeComposer.endCodeFragment(); 159 codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute); 160 } 161 162 163 /** 164 * Appends the specified subroutine. 165 */ 166 private void inlineSubroutine(Clazz clazz, 167 Method method, 168 CodeAttribute codeAttribute, 169 int subroutineInvocationOffset, 170 int subroutineStart) 171 { 172 int subroutineEnd = branchTargetFinder.subroutineEnd(subroutineStart); 173 174 if (DEBUG) 175 { 176 System.out.println(" Inlining subroutine ["+subroutineStart+" -> "+subroutineEnd+"] at ["+subroutineInvocationOffset+"]"); 177 } 178 179 // Don't go inlining exceptions that are already applicable to this 180 // subroutine invocation. 181 ExceptionInfoVisitor oldSubroutineExceptionInliner = subroutineExceptionInliner; 182 int oldClipStart = clipStart; 183 int oldClipEnd = clipEnd; 184 185 subroutineExceptionInliner = 186 new ExceptionExcludedOffsetFilter(subroutineInvocationOffset, 187 subroutineExceptionInliner); 188 clipStart = subroutineStart; 189 clipEnd = subroutineEnd; 190 191 codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); 192 193 // Copy the subroutine instructions, inlining any subroutine calls 194 // recursively. 195 codeAttribute.instructionsAccept(clazz, 196 method, 197 subroutineStart, 198 subroutineEnd, 199 this); 200 201 if (DEBUG) 202 { 203 System.out.println(" Appending label after inlined subroutine at ["+subroutineEnd+"]"); 204 } 205 206 // Append a label just after the code. 207 codeAttributeComposer.appendLabel(subroutineEnd); 208 209 // Inline the subroutine exceptions. 210 codeAttribute.exceptionsAccept(clazz, 211 method, 212 subroutineStart, 213 subroutineEnd, 214 subroutineExceptionInliner); 215 216 // We can again inline exceptions that are applicable to this 217 // subroutine invocation. 218 subroutineExceptionInliner = oldSubroutineExceptionInliner; 219 clipStart = oldClipStart; 220 clipEnd = oldClipEnd; 221 222 codeAttributeComposer.endCodeFragment(); 223 } 224 225 226 // Implementations for InstructionVisitor. 227 228 public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) 229 { 230 if (branchTargetFinder.isSubroutineStart(offset)) 231 { 232 if (DEBUG) 233 { 234 System.out.println(" Replacing first subroutine instruction "+instruction.toString(offset)+" by a label"); 235 } 236 237 // Append a label at this offset instead of saving the subroutine 238 // return address. 239 codeAttributeComposer.appendLabel(offset); 240 } 241 else 242 { 243 // Append the instruction. 244 codeAttributeComposer.appendInstruction(offset, instruction); 245 } 246 } 247 248 249 public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) 250 { 251 byte opcode = variableInstruction.opcode; 252 if (opcode == InstructionConstants.OP_RET) 253 { 254 // Is the return instruction the last instruction of the subroutine? 255 if (branchTargetFinder.subroutineEnd(offset) == offset + variableInstruction.length(offset)) 256 { 257 if (DEBUG) 258 { 259 System.out.println(" Replacing subroutine return at ["+offset+"] by a label"); 260 } 261 262 // Append a label at this offset instead of the subroutine return. 263 codeAttributeComposer.appendLabel(offset); 264 } 265 else 266 { 267 if (DEBUG) 268 { 269 System.out.println(" Replacing subroutine return at ["+offset+"] by a simple branch"); 270 } 271 272 // Replace the instruction by a branch. 273 Instruction replacementInstruction = 274 new BranchInstruction(InstructionConstants.OP_GOTO, 275 branchTargetFinder.subroutineEnd(offset) - offset); 276 277 codeAttributeComposer.appendInstruction(offset, replacementInstruction); 278 } 279 } 280 else if (branchTargetFinder.isSubroutineStart(offset)) 281 { 282 if (DEBUG) 283 { 284 System.out.println(" Replacing first subroutine instruction "+variableInstruction.toString(offset)+" by a label"); 285 } 286 287 // Append a label at this offset instead of saving the subroutine 288 // return address. 289 codeAttributeComposer.appendLabel(offset); 290 } 291 else 292 { 293 // Append the instruction. 294 codeAttributeComposer.appendInstruction(offset, variableInstruction); 295 } 296 } 297 298 299 public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) 300 { 301 byte opcode = branchInstruction.opcode; 302 if (opcode == InstructionConstants.OP_JSR || 303 opcode == InstructionConstants.OP_JSR_W) 304 { 305 int branchOffset = branchInstruction.branchOffset; 306 int branchTarget = offset + branchOffset; 307 308 // Is the subroutine ever returning? 309 if (branchTargetFinder.isSubroutineReturning(branchTarget)) 310 { 311 // Append a label at this offset instead of the subroutine invocation. 312 codeAttributeComposer.appendLabel(offset); 313 314 // Inline the invoked subroutine. 315 inlineSubroutine(clazz, 316 method, 317 codeAttribute, 318 offset, 319 branchTarget); 320 } 321 else 322 { 323 if (DEBUG) 324 { 325 System.out.println("Replacing subroutine invocation at ["+offset+"] by a simple branch"); 326 } 327 328 // Replace the subroutine invocation by a simple branch. 329 Instruction replacementInstruction = 330 new BranchInstruction(InstructionConstants.OP_GOTO, 331 branchOffset); 332 333 codeAttributeComposer.appendInstruction(offset, replacementInstruction); 334 } 335 } 336 else 337 { 338 // Append the instruction. 339 codeAttributeComposer.appendInstruction(offset, branchInstruction); 340 } 341 } 342 343 344 // Implementations for ExceptionInfoVisitor. 345 346 public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) 347 { 348 int startPC = Math.max(exceptionInfo.u2startPC, clipStart); 349 int endPC = Math.min(exceptionInfo.u2endPC, clipEnd); 350 int handlerPC = exceptionInfo.u2handlerPC; 351 int catchType = exceptionInfo.u2catchType; 352 353 // Exclude any subroutine invocations that jump out of the try block, 354 // by adding a try block before (and later on, after) each invocation. 355 for (int offset = startPC; offset < endPC; offset++) 356 { 357 if (branchTargetFinder.isSubroutineInvocation(offset)) 358 { 359 Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); 360 int instructionLength = instruction.length(offset); 361 362 // Is it a subroutine invocation? 363 if (!exceptionInfo.isApplicable(offset + ((BranchInstruction)instruction).branchOffset)) 364 { 365 if (DEBUG) 366 { 367 System.out.println(" Appending extra exception ["+startPC+" -> "+offset+"] -> "+handlerPC); 368 } 369 370 // Append a try block that ends before the subroutine invocation. 371 codeAttributeComposer.appendException(new ExceptionInfo(startPC, 372 offset, 373 handlerPC, 374 catchType)); 375 376 // The next try block will start after the subroutine invocation. 377 startPC = offset + instructionLength; 378 } 379 } 380 } 381 382 if (DEBUG) 383 { 384 if (startPC == exceptionInfo.u2startPC && 385 endPC == exceptionInfo.u2endPC) 386 { 387 System.out.println(" Appending exception ["+startPC+" -> "+endPC+"] -> "+handlerPC); 388 } 389 else 390 { 391 System.out.println(" Appending clipped exception ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+"] ~> ["+startPC+" -> "+endPC+"] -> "+handlerPC); 392 } 393 } 394 395 // Append the exception. Note that exceptions with empty try blocks 396 // are automatically ignored. 397 codeAttributeComposer.appendException(new ExceptionInfo(startPC, 398 endPC, 399 handlerPC, 400 catchType)); 401 } 402} 403