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