1 /* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2009 Eric Lafortune (eric (at) 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 */ 21 package proguard.classfile.editor; 22 23 import proguard.classfile.*; 24 import proguard.classfile.attribute.*; 25 import proguard.classfile.attribute.visitor.AttributeVisitor; 26 import proguard.classfile.constant.*; 27 import proguard.classfile.constant.visitor.ConstantVisitor; 28 import proguard.classfile.instruction.*; 29 import proguard.classfile.instruction.visitor.InstructionVisitor; 30 import proguard.classfile.util.*; 31 import proguard.classfile.visitor.*; 32 33 /** 34 * This AttributeVisitor fixes all inappropriate special/virtual/static/interface 35 * invocations of the code attributes that it visits. 36 * 37 * @author Eric Lafortune 38 */ 39 public class MethodInvocationFixer 40 extends SimplifiedVisitor 41 implements AttributeVisitor, 42 InstructionVisitor, 43 ConstantVisitor, 44 ClassVisitor, 45 MemberVisitor 46 { 47 private static final boolean DEBUG = false; 48 49 50 private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); 51 52 // Return values for the visitor methods. 53 private Clazz referencedClass; 54 private Clazz referencedMethodClass; 55 private Member referencedMethod; 56 57 58 // Implementations for AttributeVisitor. 59 60 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 61 62 63 public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) 64 { 65 // Reset the code attribute editor. 66 codeAttributeEditor.reset(codeAttribute.u4codeLength); 67 68 // Remap the variables of the instructions. 69 codeAttribute.instructionsAccept(clazz, method, this); 70 71 // Apply the code atribute editor. 72 codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); 73 } 74 75 76 // Implementations for InstructionVisitor. 77 78 public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} 79 80 81 public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) 82 { 83 int constantIndex = constantInstruction.constantIndex; 84 85 // Get information on the called class and method, if present. 86 referencedMethod = null; 87 88 clazz.constantPoolEntryAccept(constantIndex, this); 89 90 // Did we find the called class and method? 91 if (referencedMethod != null) 92 { 93 // Do we need to update the opcode? 94 byte opcode = constantInstruction.opcode; 95 96 // Is the method static? 97 if ((referencedMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0) 98 { 99 // But is it not a static invocation? 100 if (opcode != InstructionConstants.OP_INVOKESTATIC) 101 { 102 // Replace the invocation by an invokestatic instruction. 103 Instruction replacementInstruction = 104 new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 105 constantIndex).shrink(); 106 107 codeAttributeEditor.replaceInstruction(offset, replacementInstruction); 108 109 if (DEBUG) 110 { 111 debug(clazz, method, offset, constantInstruction, replacementInstruction); 112 } 113 } 114 } 115 116 // Is the method private, or an instance initializer? 117 else if ((referencedMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_PRIVATE) != 0 || 118 referencedMethod.getName(referencedMethodClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)) 119 { 120 // But is it not a special invocation? 121 if (opcode != InstructionConstants.OP_INVOKESPECIAL) 122 { 123 // Replace the invocation by an invokespecial instruction. 124 Instruction replacementInstruction = 125 new ConstantInstruction(InstructionConstants.OP_INVOKESPECIAL, 126 constantIndex).shrink(); 127 128 codeAttributeEditor.replaceInstruction(offset, replacementInstruction); 129 130 if (DEBUG) 131 { 132 debug(clazz, method, offset, constantInstruction, replacementInstruction); 133 } 134 } 135 } 136 137 // Is the method an interface method? 138 else if ((referencedClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE) != 0) 139 { 140 int invokeinterfaceConstant = 141 (ClassUtil.internalMethodParameterSize(referencedMethod.getDescriptor(referencedMethodClass), false)) << 8; 142 143 // But is it not an interface invocation, or is the parameter 144 // size incorrect? 145 if (opcode != InstructionConstants.OP_INVOKEINTERFACE || 146 constantInstruction.constant != invokeinterfaceConstant) 147 { 148 // Fix the parameter size of the interface invocation. 149 Instruction replacementInstruction = 150 new ConstantInstruction(InstructionConstants.OP_INVOKEINTERFACE, 151 constantIndex, 152 invokeinterfaceConstant).shrink(); 153 154 codeAttributeEditor.replaceInstruction(offset, replacementInstruction); 155 156 if (DEBUG) 157 { 158 debug(clazz, method, offset, constantInstruction, replacementInstruction); 159 } 160 } 161 } 162 163 // The method is not static, private, an instance initializer, or 164 // an interface method. 165 else 166 { 167 // But is it not a virtual invocation (or a special invocation, 168 // but not a super call)? 169 if (opcode != InstructionConstants.OP_INVOKEVIRTUAL && 170 (opcode != InstructionConstants.OP_INVOKESPECIAL || 171 !clazz.extends_(referencedClass))) 172 { 173 // Replace the invocation by an invokevirtual instruction. 174 Instruction replacementInstruction = 175 new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 176 constantIndex).shrink(); 177 178 codeAttributeEditor.replaceInstruction(offset, replacementInstruction); 179 180 if (DEBUG) 181 { 182 debug(clazz, method, offset, constantInstruction, replacementInstruction); 183 } 184 } 185 } 186 } 187 } 188 189 190 // Implementations for ConstantVisitor. 191 192 public void visitAnyConstant(Clazz clazz, Constant constant) {} 193 194 195 public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant) 196 { 197 // Check if this is an interface method. Note that we're interested in 198 // the class of the method reference, not in the class in which the 199 // method was actually found. 200 //refConstant.referencedClassAccept(this); 201 clazz.constantPoolEntryAccept(refConstant.u2classIndex, this); 202 203 // Get the referenced access flags and names. 204 refConstant.referencedMemberAccept(this); 205 } 206 207 208 public void visitClassConstant(Clazz clazz, ClassConstant classConstant) 209 { 210 // Check if this is an interface class. 211 classConstant.referencedClassAccept(this); 212 } 213 214 215 // Implementations for ClassVisitor. 216 217 public void visitAnyClass(Clazz clazz) 218 { 219 // Remember the referenced class. 220 referencedClass = clazz; 221 } 222 223 224 // Implementations for MemberVisitor. 225 226 public void visitAnyMember(Clazz clazz, Member member) 227 { 228 // Remember the referenced method. 229 referencedMethodClass = clazz; 230 referencedMethod = member; 231 } 232 233 234 // Small utility methods. 235 236 private void debug(Clazz clazz, 237 Method method, 238 int offset, 239 ConstantInstruction constantInstruction, 240 Instruction replacementInstruction) 241 { 242 System.out.println("MethodInvocationFixer:"); 243 System.out.println(" Class = "+clazz.getName()); 244 System.out.println(" Method = "+method.getName(clazz)+method.getDescriptor(clazz)); 245 System.out.println(" Instruction = "+constantInstruction.toString(offset)); 246 System.out.println(" -> Class = "+referencedClass); 247 System.out.println(" Method = "+referencedMethod); 248 if ((referencedClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE) != 0) 249 { 250 System.out.println(" Parameter size = "+(ClassUtil.internalMethodParameterSize(referencedMethod.getDescriptor(referencedMethodClass), false))); 251 } 252 System.out.println(" Replacement instruction = "+replacementInstruction.toString(offset)); 253 } 254 } 255