/*
 * 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.Handle;
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.classfile.engine.asm.FindBugsASM;
import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName;
import edu.umd.cs.findbugs.util.ClassName;

Author:William Pugh
/** * @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;
Author:pugh
/** * @author pugh */
private final class ClassParserMethodVisitor extends AbstractMethodVisitor { /** * */ private final TreeSet<ClassDescriptor> calledClassSet; /** * */ private final edu.umd.cs.findbugs.classfile.analysis.MethodInfo.Builder mBuilder; /** * */ private final String methodName; /** * */ private final int access; /** * */ private final String methodDesc; /** * */ private final edu.umd.cs.findbugs.classfile.analysis.ClassNameAndSuperclassInfo.Builder cBuilder; boolean sawReturn; 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; String bridgedMethodSignature; IdentityMethodState identityState = IdentityMethodState.INITIAL; ParameterLoadState parameterLoadState = ParameterLoadState.OTHER; int parameterForLoadState; StubState stubState = StubState.INITIAL; boolean isAccessMethod; String accessOwner, accessName, accessDesc; boolean accessForField; boolean accessIsStatic; HashSet<Label> labelsSeen = new HashSet<Label>();
Params:
  • calledClassSet –
  • mBuilder –
  • methodName –
  • access –
  • methodDesc –
  • cBuilder –
/** * @param calledClassSet * @param mBuilder * @param methodName * @param access * @param methodDesc * @param cBuilder */
private ClassParserMethodVisitor(TreeSet<ClassDescriptor> calledClassSet, edu.umd.cs.findbugs.classfile.analysis.MethodInfo.Builder mBuilder, String methodName, int access, String methodDesc, edu.umd.cs.findbugs.classfile.analysis.ClassNameAndSuperclassInfo.Builder cBuilder) { this.calledClassSet = calledClassSet; this.mBuilder = mBuilder; this.methodName = methodName; this.access = access; this.methodDesc = methodDesc; this.cBuilder = cBuilder; sawReturn = (access & Opcodes.ACC_NATIVE) != 0; isBridge = (access & Opcodes.ACC_SYNTHETIC) != 0 && (access & Opcodes.ACC_BRIDGE) != 0; isAccessMethod = methodName.startsWith("access$"); } 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; default: 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(); } @Override 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 visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { mBuilder.setUsesInvokeDynamic(); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 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 && "java/lang/RuntimeException".equals(owner) && "<init>".equals(name)) { 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 && "java/lang/System".equals(owner) && "exit".equals(name) && !sawReturn) { sawSystemExit = true; } justSawInitializationOfUnsupportedOperationException = opcode == Opcodes.INVOKESPECIAL && "java/lang/UnsupportedOperationException".equals(owner) && "<init>".equals(name); 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; } break; default: break; } } // 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); } @Override 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(); } } @Override 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(); } } 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; } @Override public void parse(final ClassNameAndSuperclassInfo.Builder cBuilder) throws InvalidClassFileFormatException { cBuilder.setCodeBaseEntry(codeBaseEntry); final TreeSet<ClassDescriptor> calledClassSet = new TreeSet<ClassDescriptor>(); classReader.accept(new ClassVisitor(FindBugsASM.ASM_VERSION) { // boolean isInnerClass = false; @Override 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); } } @Override 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; } @Override public void visitAttribute(Attribute arg0) { // } @Override public void visitEnd() { // } @Override 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() { @Override public org.objectweb.asm.AnnotationVisitor visitAnnotation(final String desc, boolean visible) { AnnotationValue value = new AnnotationValue(desc); fBuilder.addAnnotation(desc, value); return value.getAnnotationVisitor(); } @Override public void visitEnd() { cBuilder2.addFieldDescriptor(fBuilder.build()); } }; } return null; } @Override 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); } } } @Override 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 ClassParserMethodVisitor(calledClassSet, mBuilder, methodName, access, methodDesc, cBuilder); } return null; } @Override public void visitOuterClass(String owner, String name, String desc) { } @Override 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: case Constants.CONSTANT_InvokeDynamic: 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 Constants.CONSTANT_String: case Constants.CONSTANT_MethodType: size = 3; break; case Constants.CONSTANT_MethodHandle: size = 4; 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); } @Override public void parse(ClassInfo.Builder builder) throws InvalidClassFileFormatException { parse((ClassNameAndSuperclassInfo.Builder) builder); } }