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