MethodInvocationClassVisitor.java
/*
* Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Additionally licensed with:
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.spf4j.base.asm;
import com.google.common.collect.Maps;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* The purpose of this visitor is to find all incovation of certain method. Also to try to get the invocation parameters
* in case they are know at compile time. Since not all stack operations are simulated yet, the invocation parameters
* might not be accurate.
*
* @author zoly
*/
class MethodInvocationClassVisitor extends ClassVisitor {
private final Collection<Invocation> addCaleesTo;
private final Map<String, Method> methodStrings;
private String className;
private String source;
MethodInvocationClassVisitor(final Collection addCaleesTo,
final Set<Method> methods) {
super(Opcodes.ASM5);
this.addCaleesTo = addCaleesTo;
this.methodStrings = Maps.newHashMapWithExpectedSize(methods.size());
for (Method m : methods) {
this.methodStrings.put(TypeUtils.toString(m), m);
}
}
@Override
public void visit(final int version, final int access, final String name,
final String signature, final String superName, final String[] interfaces) {
className = name;
}
@Override
public void visitSource(final String psource, final String debug) {
this.source = psource;
}
@Override
public MethodVisitor visitMethod(final int access, final String methodName,
final String methodDesc, final String signature,
final String[] exceptions) {
return new MethodVisitorImpl(Opcodes.ASM5, methodDesc, methodName);
}
@Override
public String toString() {
return "MethodInvocationClassVisitor{" + "addCaleesTo=" + addCaleesTo + ", methodStrings=" + methodStrings
+ ", className=" + className + ", source=" + source + '}';
}
private class MethodVisitorImpl extends MethodVisitor {
private final String methodDesc;
private final String methodName;
private int lineNumber;
private final Stack<Object> stack;
MethodVisitorImpl(final int api, final String methodDesc, final String methodName) {
super(api);
this.methodDesc = methodDesc;
this.methodName = methodName;
this.stack = new Stack<>();
Type[] argumentTypes = Type.getArgumentTypes(methodDesc);
for (int i = 0; i < argumentTypes.length; i++) {
stack.add(new UnknownValue(-1));
}
}
@Override
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc,
final boolean itf) {
final Method invokedMethod = methodStrings.get(owner + '/' + name + desc);
Type returnType = Type.getReturnType(desc);
Type[] parameterTypes = Type.getArgumentTypes(desc);
if (invokedMethod != null) {
Object[] parameters = new Object[parameterTypes.length];
for (int i = parameterTypes.length - 1; i >= 0; i--) {
if (stack.isEmpty()) {
throw new IllegalStateException("Not enough params in stack for invocation of " + desc + " at "
+ className + '.' + methodName + '.' + lineNumber);
} else {
parameters[i] = stack.pop();
}
}
addCaleesTo.add(new Invocation(className, methodName, methodDesc, parameters,
source, lineNumber, invokedMethod));
if (opcode != Opcodes.INVOKESTATIC && !stack.isEmpty()) {
stack.pop();
}
} else {
int length = parameterTypes.length;
if (opcode != Opcodes.INVOKESTATIC) {
length++;
}
for (int i = 0; i < length; i++) {
if (!stack.isEmpty()) {
stack.pop();
}
}
}
if (returnType != Type.VOID_TYPE) {
stack.push(new UnknownValue(opcode)); // return value
}
}
@Override
public void visitLdcInsn(final Object o) {
stack.push(o); // push the constant to the stack.
}
@Override
public void visitTypeInsn(final int opcode, final String type) {
stack.push(new UnknownValue(opcode));
}
@Override
@SuppressFBWarnings({"PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS", "CC_CYCLOMATIC_COMPLEXITY"})
public void visitInsn(final int opcode) {
Object pop1 = null;
Object pop2 = null;
switch (opcode) {
case Opcodes.ICONST_0:
stack.push(0);
break;
case Opcodes.ICONST_1:
stack.push(1);
break;
case Opcodes.ICONST_2:
stack.push(2);
break;
case Opcodes.ICONST_3:
stack.push(3);
break;
case Opcodes.ICONST_4:
stack.push(4);
break;
case Opcodes.ICONST_5:
stack.push(5);
break;
case Opcodes.ICONST_M1:
stack.push(-1);
break;
case Opcodes.LCONST_0:
stack.push(0L);
break;
case Opcodes.LCONST_1:
stack.push(1L);
break;
case Opcodes.FCONST_0:
stack.push(0f);
break;
case Opcodes.FCONST_1:
stack.push(1f);
break;
case Opcodes.FCONST_2:
stack.push(2f);
break;
case Opcodes.DCONST_0:
stack.push(0d);
break;
case Opcodes.DCONST_1:
stack.push(1d);
break;
case Opcodes.ACONST_NULL:
stack.push(null);
break;
case Opcodes.DUP:
case Opcodes.DUP2: // we do not differentiate betwen types with mutiple words.
Object pop = stack.peek();
stack.push(pop);
break;
case Opcodes.DUP2_X1:
case Opcodes.DUP_X1:
Object a1 = stack.peek();
Object b1 = stack.peek();
stack.push(a1);
stack.push(b1);
stack.push(a1);
break;
case Opcodes.DUP2_X2:
case Opcodes.DUP_X2:
Object a2 = stack.peek();
Object b2 = stack.peek();
Object c2 = stack.peek();
stack.push(a2);
stack.push(c2);
stack.push(b2);
stack.push(a2);
break;
case Opcodes.IDIV:
pop1 = stack.pop();
pop2 = stack.pop();
try {
stack.push((int) pop2 / (int) pop1);
} catch (RuntimeException ex) {
stack.push(new UnknownValue(opcode));
}
break;
case Opcodes.IMUL:
pop1 = stack.pop();
pop2 = stack.pop();
try {
stack.push((int) pop2 * (int) pop1);
} catch (RuntimeException ex) {
stack.push(new UnknownValue(opcode));
}
break;
case Opcodes.IADD:
pop1 = stack.pop();
pop2 = stack.pop();
try {
stack.push((int) pop2 + (int) pop1);
} catch (RuntimeException ex) {
stack.push(new UnknownValue(opcode));
}
break;
case Opcodes.ISUB:
pop1 = stack.pop();
pop2 = stack.pop();
try {
stack.push((int) pop2 - (int) pop1);
} catch (RuntimeException ex) {
stack.push(new UnknownValue(opcode));
}
break;
case Opcodes.LDIV:
pop1 = stack.pop();
pop2 = stack.pop();
try {
stack.push((long) pop2 / (long) pop1);
} catch (RuntimeException ex) {
stack.push(new UnknownValue(opcode));
}
break;
case Opcodes.LMUL:
pop1 = stack.pop();
pop2 = stack.pop();
try {
stack.push((long) pop2 * (long) pop1);
} catch (RuntimeException ex) {
stack.push(new UnknownValue(opcode));
}
break;
case Opcodes.LADD:
pop1 = stack.pop();
pop2 = stack.pop();
try {
stack.push((long) pop2 + (long) pop1);
} catch (RuntimeException ex) {
stack.push(new UnknownValue(opcode));
}
break;
case Opcodes.LSUB:
pop1 = stack.pop();
pop2 = stack.pop();
try {
stack.push((long) pop2 - (long) pop1);
} catch (RuntimeException ex) {
stack.push(new UnknownValue(opcode));
}
break;
default:
stack.push(new UnknownValue(opcode)); // assume something will end up on the stack
}
}
@Override
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
switch (opcode) {
case Opcodes.GETSTATIC:
Class<?> clasz;
try {
clasz = Class.forName(owner.replace('/', '.'));
} catch (ClassNotFoundException ex) {
stack.push(new UnknownValue(opcode)); // assume something will end up on the stack
break;
}
Field declaredField;
try {
declaredField = clasz.getDeclaredField(name);
} catch (NoSuchFieldException | SecurityException ex) {
stack.push(new UnknownValue(opcode)); // assume something will end up on the stack
break;
}
if ((declaredField.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
declaredField.setAccessible(true);
return null;
}
});
Object value;
try {
value = declaredField.get(null);
} catch (IllegalArgumentException | IllegalAccessException ex) {
stack.push(new UnknownValue(opcode)); // assume something will end up on the stack
break;
}
stack.push(value);
} else {
stack.push(new UnknownValue(opcode)); // assume something will end up on the stack
}
break;
case Opcodes.GETFIELD:
stack.push(new UnknownValue(opcode)); // assume something will end up on the stack
break;
case Opcodes.PUTSTATIC:
case Opcodes.PUTFIELD:
if (!stack.isEmpty()) {
stack.pop();
}
break;
default:
throw new IllegalStateException(" Illegal opcode = " + opcode + " in context");
}
}
@Override
public void visitIntInsn(final int opcode, final int operand) {
switch (opcode) {
case Opcodes.BIPUSH:
case Opcodes.SIPUSH:
stack.push(operand);
break;
default:
stack.push(new UnknownValue(opcode)); // assume something will end up on the stack
}
}
@Override
public void visitVarInsn(final int opcode, final int localVarIdx) {
if (opcode >= 21 && opcode <= 45) { // *LOAD* from local var instructions
stack.push(new UnknownValue(opcode));
} else if (opcode >= 54 && opcode <= 78) { // *STORE* to local var instructions
if (!stack.isEmpty()) {
stack.pop();
}
// else {
// throw new RuntimeException("Not enough params in stack for instr " + opcode + " at "
// + className + '.' + methodName + '.' + lineNumber);
// }
} else {
stack.push(new UnknownValue(opcode)); // assume something will end up on the stack
}
}
@Override
public void visitMultiANewArrayInsn(final String desc, final int dims) {
stack.push(new UnknownValue(Opcodes.MULTIANEWARRAY)); // assume something will end up on the stack
}
@Override
public void visitInvokeDynamicInsn(final String name, final String desc, final Handle bsm,
final Object... bsmArgs) {
// I am not sure this classic method handling is correct
Type[] argumentTypes = Type.getArgumentTypes(desc);
for (int i = 0; i < argumentTypes.length; i++) {
stack.pop();
}
Type returnType = Type.getReturnType(desc);
if (returnType != Type.VOID_TYPE) {
stack.push(new UnknownValue(Opcodes.INVOKEDYNAMIC)); // return value
}
}
@Override
public void visitLineNumber(final int line, final Label start) {
this.lineNumber = line;
}
}
}