/*
* deadmethods - A unused methods detector
* Copyright 2011-2017 MeBigFatGuy.com
* Copyright 2011-2017 Dave Brosius
*
* 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 com.mebigfatguy.deadmethods;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Set;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class CalledMethodRemovingMethodVisitor extends MethodVisitor {
enum State {
NONE, NEW_TOS
};
private final ClassRepository repo;
private final Set<String> methods;
private State state;
public CalledMethodRemovingMethodVisitor(ClassRepository repository, Set<String> allMethods) {
super(Opcodes.ASM5);
repo = repository;
methods = allMethods;
state = State.NONE;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
String methodInfo = owner + ":" + name + desc;
methods.remove(methodInfo);
try {
if (!owner.startsWith("[")) {
ClassInfo info = repo.getClassInfo(owner);
clearDerivedMethods(info, name + desc);
clearInheritedMethods(info, name + desc);
}
processReflection(opcode, owner, name);
} catch (IOException ioe) {
}
}
@Override
public void visitInsn(int opcode) {
state = State.NONE;
}
@Override
public void visitIntInsn(int opcode, int operand) {
state = State.NONE;
}
@Override
public void visitVarInsn(int opcode, int var) {
state = State.NONE;
}
@Override
public void visitTypeInsn(int opcode, String type) {
if ((opcode == Opcodes.CHECKCAST) && (state == State.NEW_TOS)) {
try {
ClassInfo rootInfo = repo.getClassInfo(type);
if (rootInfo != null) {
clearConstructors(rootInfo);
}
} catch (IOException ioe) {
}
}
state = State.NONE;
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
state = State.NONE;
}
@Override
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
for (Object o : bsmArgs) {
if (o instanceof Handle) {
Handle handle = (Handle) o;
String methodInfo = handle.getOwner() + ":" + handle.getName() + handle.getDesc();
methods.remove(methodInfo);
}
}
state = State.NONE;
}
@Override
public void visitJumpInsn(int opcode, Label label) {
state = State.NONE;
}
@Override
public void visitLdcInsn(Object cst) {
state = State.NONE;
}
@Override
public void visitIincInsn(int var, int increment) {
state = State.NONE;
}
@Override
public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
state = State.NONE;
}
@Override
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
state = State.NONE;
}
@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
state = State.NONE;
}
private void processReflection(int opcode, String owner, String name) {
if (opcode != Opcodes.INVOKEVIRTUAL) {
state = State.NONE;
return;
}
if ("newInstance".equals(name) && owner.equals(Constructor.class.getName().replaceAll("\\.", "/"))) {
state = State.NEW_TOS;
}
}
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
}
private void clearDerivedMethods(ClassInfo info, String methodInfo) {
Set<ClassInfo> derivedInfos = info.getDerivedClasses();
for (ClassInfo derivedInfo : derivedInfos) {
methods.remove(derivedInfo.getClassName() + ":" + methodInfo);
clearDerivedMethods(derivedInfo, methodInfo);
}
}
private void clearInheritedMethods(ClassInfo info, String methodInfo) throws IOException {
ClassInfo superInfo = repo.getClassInfo(info.getSuperClassName());
while (superInfo != null) {
methods.remove(superInfo.getClassName() + ":" + methodInfo);
clearInheritedMethods(superInfo, methodInfo);
superInfo = repo.getClassInfo(superInfo.getSuperClassName());
}
for (String interfaceName : info.getInterfaceNames()) {
ClassInfo infInfo = repo.getClassInfo(interfaceName);
while (infInfo != null) {
methods.remove(infInfo.getClassName() + ":" + methodInfo);
clearInheritedMethods(infInfo, methodInfo);
infInfo = repo.getClassInfo(infInfo.getSuperClassName());
}
}
}
private void clearConstructors(ClassInfo info) {
for (MethodInfo methodInfo : info.getMethodInfo()) {
if ("<init>".equals(methodInfo.getMethodName())) {
methods.remove(info.getClassName() + ":" + methodInfo);
}
}
Set<ClassInfo> derivedInfos = info.getDerivedClasses();
for (ClassInfo derivedInfo : derivedInfos) {
clearConstructors(derivedInfo);
}
}
}