/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2003-2008 University of Maryland
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.classfile.engine;
import java.util.BitSet;
import java.util.HashSet;
import java.util.TreeSet;
import javax.annotation.CheckForNull;
import org.apache.bcel.Constants;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import edu.umd.cs.findbugs.ba.SignatureParser;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.ICodeBaseEntry;
import edu.umd.cs.findbugs.classfile.InvalidClassFileFormatException;
import edu.umd.cs.findbugs.classfile.analysis.AnnotationValue;
import edu.umd.cs.findbugs.classfile.analysis.ClassInfo;
import edu.umd.cs.findbugs.classfile.analysis.ClassInfo.Builder;
import edu.umd.cs.findbugs.classfile.analysis.ClassNameAndSuperclassInfo;
import edu.umd.cs.findbugs.classfile.analysis.FieldInfo;
import edu.umd.cs.findbugs.classfile.analysis.MethodInfo;
import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName;
import edu.umd.cs.findbugs.util.ClassName;
/**
* @author William Pugh
*/
public class ClassParserUsingASM implements ClassParserInterface {
// static final boolean NO_SHIFT_INNER_CLASS_CTOR =
// SystemProperties.getBoolean("classparser.noshift");
private static final BitSet RETURN_OPCODE_SET = new BitSet();
static {
RETURN_OPCODE_SET.set(Constants.ARETURN);
RETURN_OPCODE_SET.set(Constants.IRETURN);
RETURN_OPCODE_SET.set(Constants.LRETURN);
RETURN_OPCODE_SET.set(Constants.DRETURN);
RETURN_OPCODE_SET.set(Constants.FRETURN);
RETURN_OPCODE_SET.set(Constants.RETURN);
}
private final ClassReader classReader;
private @SlashedClassName
String slashedClassName;
private final ClassDescriptor expectedClassDescriptor;
private final ICodeBaseEntry codeBaseEntry;
enum StubState {
INITIAL, LOADED_STUB, INITIALIZE_RUNTIME
}
enum IdentityMethodState {
INITIAL, LOADED_PARAMETER, NOT;
}
enum ParameterLoadState {
OTHER, LOADED_THIS, LOADED_THIS_AND_PARAMETER;
}
public ClassParserUsingASM(ClassReader classReader, @CheckForNull ClassDescriptor expectedClassDescriptor,
ICodeBaseEntry codeBaseEntry) {
this.classReader = classReader;
this.expectedClassDescriptor = expectedClassDescriptor;
this.codeBaseEntry = codeBaseEntry;
}
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.findbugs.classfile.engine.ClassParserInterface#parse(edu.umd
* .cs.findbugs.classfile.analysis.ClassNameAndSuperclassInfo.Builder)
*/
public void parse(final ClassNameAndSuperclassInfo.Builder cBuilder) throws InvalidClassFileFormatException {
cBuilder.setCodeBaseEntry(codeBaseEntry);
final TreeSet<ClassDescriptor> calledClassSet = new TreeSet<ClassDescriptor>();
classReader.accept(new ClassVisitor() {
boolean isInnerClass = false;
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
ClassParserUsingASM.this.slashedClassName = name;
cBuilder.setClassfileVersion(version >>> 16, version & 0xffff);
cBuilder.setAccessFlags(access);
cBuilder.setClassDescriptor(DescriptorFactory.createClassDescriptor(name));
cBuilder.setInterfaceDescriptorList(DescriptorFactory.createClassDescriptor(interfaces));
if (superName != null)
cBuilder.setSuperclassDescriptor(DescriptorFactory.createClassDescriptor(superName));
if (cBuilder instanceof ClassInfo.Builder) {
((ClassInfo.Builder) cBuilder).setSourceSignature(signature);
}
}
public org.objectweb.asm.AnnotationVisitor visitAnnotation(String desc, boolean isVisible) {
if (cBuilder instanceof ClassInfo.Builder) {
AnnotationValue value = new AnnotationValue(desc);
((ClassInfo.Builder) cBuilder).addAnnotation(desc, value);
return value.getAnnotationVisitor();
}
return null;
}
public void visitAttribute(Attribute arg0) {
// TODO Auto-generated method stub
}
public void visitEnd() {
// TODO Auto-generated method stub
}
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if (name.equals("this$0"))
isInnerClass = true;
if (desc == null)
throw new NullPointerException("Description cannot be null");
if (cBuilder instanceof ClassInfo.Builder) {
final ClassInfo.Builder cBuilder2 = (ClassInfo.Builder) cBuilder;
if ((access & Opcodes.ACC_VOLATILE) != 0 || desc.contains("util/concurrent"))
cBuilder2.setUsesConcurrency();
final FieldInfo.Builder fBuilder = new FieldInfo.Builder(slashedClassName, name, desc, access);
fBuilder.setSourceSignature(signature);
return new AbstractFieldAnnotationVisitor() {
public org.objectweb.asm.AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
AnnotationValue value = new AnnotationValue(desc);
fBuilder.addAnnotation(desc, value);
return value.getAnnotationVisitor();
}
public void visitEnd() {
cBuilder2.addFieldDescriptor(fBuilder.build());
}
};
}
return null;
}
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (name.equals(slashedClassName) && outerName != null) {
if (cBuilder instanceof ClassInfo.Builder) {
ClassDescriptor outerClassDescriptor = DescriptorFactory.createClassDescriptor(outerName);
((ClassInfo.Builder) cBuilder).setImmediateEnclosingClass(outerClassDescriptor);
((ClassInfo.Builder) cBuilder).setAccessFlags(access);
}
}
}
public MethodVisitor visitMethod(final int access, final String methodName, final String methodDesc,
String signature, String[] exceptions) {
if (cBuilder instanceof ClassInfo.Builder) {
final MethodInfo.Builder mBuilder = new MethodInfo.Builder(slashedClassName, methodName, methodDesc, access);
mBuilder.setSourceSignature(signature);
mBuilder.setThrownExceptions(exceptions);
if ((access & Opcodes.ACC_SYNCHRONIZED) != 0)
mBuilder.setUsesConcurrency();
return new AbstractMethodVisitor() {
boolean sawReturn = (access & Opcodes.ACC_NATIVE) != 0;
boolean sawNormalThrow = false;
boolean sawUnsupportedThrow = false;
boolean sawSystemExit = false;
boolean sawBranch = false;
boolean sawBackBranch = false;
int methodCallCount = 0;
int fieldInstructionCount = 0;
boolean sawStubThrow = false;
boolean justSawInitializationOfUnsupportedOperationException;
boolean isBridge = (access & Opcodes.ACC_SYNTHETIC) != 0 && (access & Opcodes.ACC_BRIDGE) != 0;
String bridgedMethodSignature;
IdentityMethodState identityState =
IdentityMethodState.INITIAL;
ParameterLoadState parameterLoadState = ParameterLoadState.OTHER;
int parameterForLoadState;
StubState stubState = StubState.INITIAL;
boolean isAccessMethod = methodName.startsWith("access$");
String accessOwner, accessName, accessDesc;
boolean accessForField;
boolean accessIsStatic;
HashSet<Label> labelsSeen = new HashSet<Label>();
boolean isStatic() {
return (access & Opcodes.ACC_STATIC) != 0;
}
@Override
public
void visitLocalVariable(String name,
String desc,
String signature,
Label start,
Label end,
int index) {
mBuilder.setVariableHasName(index);
}
@Override
public void visitLdcInsn(Object cst) {
if (cst.equals("Stub!"))
stubState = StubState.LOADED_STUB;
else
stubState = StubState.INITIAL;
identityState = IdentityMethodState.NOT;
}
@Override
public void visitInsn(int opcode) {
switch (opcode) {
case Constants.MONITORENTER:
mBuilder.setUsesConcurrency();
break;
case Constants.ARETURN:
case Constants.IRETURN:
case Constants.LRETURN:
case Constants.DRETURN:
case Constants.FRETURN:
if (identityState == IdentityMethodState.LOADED_PARAMETER)
mBuilder.setIsIdentity();
sawReturn = true;
break;
case Constants.RETURN:
sawReturn = true;
break;
case Constants.ATHROW:
if (stubState == StubState.INITIALIZE_RUNTIME)
sawStubThrow = true;
else if (justSawInitializationOfUnsupportedOperationException)
sawUnsupportedThrow = true;
else
sawNormalThrow = true;
break;
}
resetState();
}
public void resetState() {
stubState = StubState.INITIAL;
}
@Override
public void visitSomeInsn() {
identityState = IdentityMethodState.NOT;
parameterLoadState = ParameterLoadState.OTHER;
resetState();
}
@Override
public void visitVarInsn(int opcode, int var) {
boolean match = false;
if (parameterLoadState == ParameterLoadState.OTHER && !isStatic() && var == 0) {
parameterLoadState = ParameterLoadState.LOADED_THIS;
match = true;
}
else if (parameterLoadState == ParameterLoadState.LOADED_THIS && var > 0){
parameterLoadState = ParameterLoadState.LOADED_THIS_AND_PARAMETER;
parameterForLoadState = var;
match = true;
}
if (identityState == IdentityMethodState.INITIAL) {
match = true;
if (var > 0 || isStatic())
identityState = IdentityMethodState.LOADED_PARAMETER;
else
identityState = IdentityMethodState.NOT;
}
if (!match)
visitSomeInsn();
}
@Override
public void visitFieldInsn(int opcode,
String owner,
String name,
String desc) {
if (opcode == Opcodes.PUTFIELD && parameterLoadState == ParameterLoadState.LOADED_THIS_AND_PARAMETER
&& owner.equals(slashedClassName) && name.startsWith("this$")) {
mBuilder.setVariableIsSynthetic(parameterForLoadState);
}
fieldInstructionCount++;
if (isAccessMethod && this.accessOwner == null) {
this.accessOwner = owner;
this.accessName = name;
this.accessDesc = desc;
this.accessIsStatic = opcode == Opcodes.GETSTATIC || opcode == Opcodes.PUTSTATIC;
this.accessForField = true;
}
visitSomeInsn();
}
public org.objectweb.asm.AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
AnnotationValue value = new AnnotationValue(desc);
mBuilder.addAnnotation(desc, value);
return value.getAnnotationVisitor();
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
identityState = IdentityMethodState.NOT;
methodCallCount++;
if (isAccessMethod && this.accessOwner == null) {
this.accessOwner = owner;
this.accessName = name;
this.accessDesc = desc;
this.accessIsStatic = opcode == Opcodes.INVOKESTATIC;
this.accessForField = false;
}
if (stubState == StubState.LOADED_STUB && opcode == Opcodes.INVOKESPECIAL
&& owner.equals("java/lang/RuntimeException") && name.equals("<init>"))
stubState = StubState.INITIALIZE_RUNTIME;
else
stubState = StubState.INITIAL;
if (owner.startsWith("java/util/concurrent"))
mBuilder.setUsesConcurrency();
if (opcode == Opcodes.INVOKEINTERFACE)
return;
if (owner.charAt(0) == '[' && owner.charAt(owner.length() - 1) != ';') {
// primitive array
return;
}
if (opcode == Opcodes.INVOKESTATIC && owner.equals("java/lang/System") && name.equals("exit")
&& !sawReturn)
sawSystemExit = true;
justSawInitializationOfUnsupportedOperationException = opcode == Opcodes.INVOKESPECIAL
&& owner.equals("java/lang/UnsupportedOperationException") && name.equals("<init>");
if (isBridge && bridgedMethodSignature == null)
switch (opcode) {
case Opcodes.INVOKEVIRTUAL:
case Opcodes.INVOKESPECIAL:
case Opcodes.INVOKESTATIC:
case Opcodes.INVOKEINTERFACE:
if (desc != null && name.equals(methodName))
bridgedMethodSignature = desc;
}
// System.out.println("Call from " +
// ClassParserUsingASM.this.slashedClassName +
// " to " + owner + " : " + desc);
if (desc == null || desc.indexOf('[') == -1 && desc.indexOf('L') == -1)
return;
if (ClassParserUsingASM.this.slashedClassName.equals(owner))
return;
ClassDescriptor classDescriptor = DescriptorFactory.instance().getClassDescriptor(owner);
calledClassSet.add(classDescriptor);
}
private void sawBranchTo(Label label) {
sawBranch = true;
if (labelsSeen.contains(label))
sawBackBranch = true;
}
@Override
public void visitJumpInsn(int opcode, Label label) {
sawBranchTo(label);
identityState = IdentityMethodState.NOT;
super.visitJumpInsn(opcode, label);
}
@Override
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
sawBranchTo(dflt);
for (Label lbl : labels)
sawBranchTo(lbl);
identityState = IdentityMethodState.NOT;
super.visitLookupSwitchInsn(dflt, keys, labels);
}
@Override
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
sawBranchTo(dflt);
for (Label lbl : labels)
sawBranchTo(lbl);
identityState = IdentityMethodState.NOT;
super.visitTableSwitchInsn(min, max, dflt, labels);
}
@Override
public void visitLabel(Label label) {
labelsSeen.add(label);
super.visitLabel(label);
}
public void visitEnd() {
labelsSeen.clear();
if (isAccessMethod && accessOwner != null) {
if (!accessForField && methodCallCount == 1) {
mBuilder.setAccessMethodForMethod(accessOwner, accessName, accessDesc, accessIsStatic);
} else if(accessForField && fieldInstructionCount == 1) {
boolean isSetter = methodDesc.endsWith(")V");
int numArg = new SignatureParser(methodDesc).getNumParameters();
int expected = 0;
if (!accessIsStatic) expected++;
if (isSetter) expected++;
boolean OK;
if (isSetter)
OK = methodDesc.substring(1).startsWith(ClassName.toSignature(accessOwner) + accessDesc);
else
OK = methodDesc.substring(1).startsWith(ClassName.toSignature(accessOwner));
if (numArg == expected && OK)
mBuilder.setAccessMethodForField(accessOwner, accessName, accessDesc, accessIsStatic);
}
}
if (sawBackBranch)
mBuilder.setHasBackBranch();
boolean sawThrow = sawNormalThrow | sawUnsupportedThrow | sawStubThrow;
if (sawThrow && !sawReturn || sawSystemExit && !sawBranch) {
mBuilder.setIsUnconditionalThrower();
if (!sawReturn && !sawNormalThrow) {
if (sawUnsupportedThrow)
mBuilder.setUnsupported();
if (sawStubThrow) {
mBuilder.addAccessFlags(Constants.ACC_SYNTHETIC);
mBuilder.setIsStub();
}
}
// else
// System.out.println(slashedClassName+"."+methodName+methodDesc
// + " is thrower");
}
mBuilder.setNumberMethodCalls(methodCallCount);
MethodInfo methodInfo = mBuilder.build();
Builder classBuilder = (ClassInfo.Builder) cBuilder;
if (isBridge && bridgedMethodSignature != null && !bridgedMethodSignature.equals(methodDesc))
classBuilder.addBridgeMethodDescriptor(methodInfo, bridgedMethodSignature);
else
classBuilder.addMethodDescriptor(methodInfo);
if (methodInfo.usesConcurrency())
classBuilder.setUsesConcurrency();
if (methodInfo.isStub())
classBuilder.setHasStubs();
}
public org.objectweb.asm.AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
boolean visible) {
AnnotationValue value = new AnnotationValue(desc);
mBuilder.addParameterAnnotation(parameter, desc, value);
return value.getAnnotationVisitor();
}
};
}
return null;
}
public void visitOuterClass(String owner, String name, String desc) {
}
public void visitSource(String arg0, String arg1) {
if (cBuilder instanceof ClassInfo.Builder) {
((ClassInfo.Builder) cBuilder).setSource(arg0);
}
}
}, ClassReader.SKIP_FRAMES);
HashSet<ClassDescriptor> referencedClassSet = new HashSet<ClassDescriptor>();
// collect class references
int constantPoolCount = classReader.readUnsignedShort(8);
int offset = 10;
char[] buf = new char[1024];
// System.out.println("constant pool count: " + constantPoolCount);
for (int count = 1; count < constantPoolCount; count++) {
int tag = classReader.readByte(offset);
int size;
switch (tag) {
case Constants.CONSTANT_Methodref:
case Constants.CONSTANT_InterfaceMethodref:
case Constants.CONSTANT_Fieldref:
case Constants.CONSTANT_Integer:
case Constants.CONSTANT_Float:
case Constants.CONSTANT_NameAndType:
size = 5;
break;
case Constants.CONSTANT_Long:
case Constants.CONSTANT_Double:
size = 9;
count++;
break;
case Constants.CONSTANT_Utf8:
size = 3 + classReader.readUnsignedShort(offset + 1);
break;
case Constants.CONSTANT_Class:
@SlashedClassName
String className = classReader.readUTF8(offset + 1, buf);
if (className.indexOf('[') >= 0) {
ClassParser.extractReferencedClassesFromSignature(referencedClassSet, className);
} else if (ClassName.isValidClassName(className)) {
ClassDescriptor classDescriptor = DescriptorFactory.instance().getClassDescriptor(className);
referencedClassSet.add(classDescriptor);
}
size = 3;
break;
// case ClassWriter.CLASS:
// case ClassWriter.STR:
case Constants.CONSTANT_String:
size = 3;
break;
default:
throw new IllegalStateException("Unexpected tag of " + tag + " at offset " + offset + " while parsing "
+ slashedClassName + " from " + codeBaseEntry);
}
// System.out.println(count + "@" + offset + " : [" + tag
// +"] size="+size);
offset += size;
}
cBuilder.setCalledClassDescriptors(calledClassSet);
cBuilder.setReferencedClassDescriptors(referencedClassSet);
}
public void parse(ClassInfo.Builder builder) throws InvalidClassFileFormatException {
parse((ClassNameAndSuperclassInfo.Builder) builder);
}
}