| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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.jetbrains.jps.builders.java.dependencyView; |
| |
| import com.intellij.openapi.util.Pair; |
| import gnu.trove.THashMap; |
| import gnu.trove.THashSet; |
| import gnu.trove.TIntHashSet; |
| import org.jetbrains.org.objectweb.asm.*; |
| import org.jetbrains.org.objectweb.asm.signature.SignatureReader; |
| import org.jetbrains.org.objectweb.asm.signature.SignatureVisitor; |
| |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.EnumSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * @author: db |
| * Date: 31.01.11 |
| */ |
| |
| class ClassfileAnalyzer { |
| private final DependencyContext myContext; |
| |
| ClassfileAnalyzer(DependencyContext context) { |
| this.myContext = context; |
| } |
| |
| private static class Holder<T> { |
| private T x = null; |
| |
| public void set(final T x) { |
| this.x = x; |
| } |
| |
| public T get() { |
| return x; |
| } |
| } |
| |
| private class ClassCrawler extends ClassVisitor { |
| private class AnnotationRetentionPolicyCrawler extends AnnotationVisitor { |
| private AnnotationRetentionPolicyCrawler() { |
| super(Opcodes.ASM5); |
| } |
| |
| public void visit(String name, Object value) { |
| } |
| |
| public void visitEnum(String name, String desc, String value) { |
| myRetentionPolicy = RetentionPolicy.valueOf(value); |
| } |
| |
| public AnnotationVisitor visitAnnotation(String name, String desc) { |
| return null; |
| } |
| |
| public AnnotationVisitor visitArray(String name) { |
| return null; |
| } |
| |
| public void visitEnd() { |
| } |
| } |
| |
| private class AnnotationTargetCrawler extends AnnotationVisitor { |
| private AnnotationTargetCrawler() { |
| super(Opcodes.ASM5); |
| } |
| |
| public void visit(String name, Object value) { |
| } |
| |
| public void visitEnum(final String name, String desc, final String value) { |
| myTargets.add(ElemType.valueOf(value)); |
| } |
| |
| public AnnotationVisitor visitAnnotation(String name, String desc) { |
| return this; |
| } |
| |
| public AnnotationVisitor visitArray(String name) { |
| return this; |
| } |
| |
| public void visitEnd() { |
| } |
| } |
| |
| private class AnnotationCrawler extends AnnotationVisitor { |
| private final TypeRepr.ClassType myType; |
| private final ElemType myTarget; |
| |
| private final TIntHashSet myUsedArguments = new TIntHashSet(); |
| |
| private AnnotationCrawler(final TypeRepr.ClassType type, final ElemType target) { |
| super(Opcodes.ASM5); |
| this.myType = type; |
| this.myTarget = target; |
| final Set<ElemType> targets = myAnnotationTargets.get(type); |
| if (targets == null) { |
| myAnnotationTargets.put(type, EnumSet.of(target)); |
| } |
| else { |
| targets.add(target); |
| } |
| myUsages.add(UsageRepr.createClassUsage(myContext, type.className)); |
| } |
| |
| private String getMethodDescr(final Object value) { |
| if (value instanceof Type) { |
| return "()Ljava/lang/Class;"; |
| } |
| |
| final String name = Type.getType(value.getClass()).getInternalName(); |
| |
| if (name.equals("java/lang/Integer")) { |
| return "()I;"; |
| } |
| |
| if (name.equals("java/lang/Short")) { |
| return "()S;"; |
| } |
| |
| if (name.equals("java/lang/Long")) { |
| return "()J;"; |
| } |
| |
| if (name.equals("java/lang/Byte")) { |
| return "()B;"; |
| } |
| |
| if (name.equals("java/lang/Char")) { |
| return "()C;"; |
| } |
| |
| if (name.equals("java/lang/Boolean")) { |
| return "()Z;"; |
| } |
| |
| if (name.equals("java/lang/Float")) { |
| return "()F;"; |
| } |
| |
| if (name.equals("java/lang/Double")) { |
| return "()D;"; |
| } |
| |
| return "()L" + name + ";"; |
| } |
| |
| public void visit(String name, Object value) { |
| final String methodDescr = getMethodDescr(value); |
| final int methodName = myContext.get(name); |
| |
| if (value instanceof Type) { |
| final String className = ((Type)value).getClassName().replace('.', '/'); |
| myUsages.add(UsageRepr.createClassUsage(myContext, myContext.get(className))); |
| } |
| |
| myUsages.add(UsageRepr.createMethodUsage(myContext, methodName, myType.className, methodDescr)); |
| myUsages.add(UsageRepr.createMetaMethodUsage(myContext, methodName, myType.className, methodDescr)); |
| |
| myUsedArguments.add(methodName); |
| } |
| |
| public void visitEnum(String name, String desc, String value) { |
| final int methodName = myContext.get(name); |
| final String methodDescr = "()" + desc; |
| |
| myUsages.add(UsageRepr.createMethodUsage(myContext, methodName, myType.className, methodDescr)); |
| myUsages.add(UsageRepr.createMetaMethodUsage(myContext, methodName, myType.className, methodDescr)); |
| |
| myUsedArguments.add(methodName); |
| } |
| |
| public AnnotationVisitor visitAnnotation(String name, String desc) { |
| return new AnnotationCrawler((TypeRepr.ClassType)TypeRepr.getType(myContext, myContext.get(desc)), myTarget); |
| } |
| |
| public AnnotationVisitor visitArray(String name) { |
| myUsedArguments.add(myContext.get(name)); |
| return this; |
| } |
| |
| public void visitEnd() { |
| final TIntHashSet s = myAnnotationArguments.get(myType); |
| |
| if (s == null) { |
| myAnnotationArguments.put(myType, myUsedArguments); |
| } |
| else { |
| s.retainAll(myUsedArguments.toArray()); |
| } |
| } |
| } |
| |
| private void processSignature(final String sig) { |
| if (sig != null) { |
| new SignatureReader(sig).accept(mySignatureCrawler); |
| } |
| } |
| |
| private final SignatureVisitor mySignatureCrawler = new SignatureVisitor(Opcodes.ASM5) { |
| public void visitFormalTypeParameter(String name) { |
| } |
| |
| public SignatureVisitor visitClassBound() { |
| return this; |
| } |
| |
| public SignatureVisitor visitInterfaceBound() { |
| return this; |
| } |
| |
| public SignatureVisitor visitSuperclass() { |
| return this; |
| } |
| |
| public SignatureVisitor visitInterface() { |
| return this; |
| } |
| |
| public SignatureVisitor visitParameterType() { |
| return this; |
| } |
| |
| public SignatureVisitor visitReturnType() { |
| return this; |
| } |
| |
| public SignatureVisitor visitExceptionType() { |
| return this; |
| } |
| |
| public void visitBaseType(char descriptor) { |
| } |
| |
| public void visitTypeVariable(String name) { |
| } |
| |
| public SignatureVisitor visitArrayType() { |
| return this; |
| } |
| |
| public void visitInnerClassType(String name) { |
| } |
| |
| public void visitTypeArgument() { |
| } |
| |
| public SignatureVisitor visitTypeArgument(char wildcard) { |
| return this; |
| } |
| |
| public void visitEnd() { |
| } |
| |
| public void visitClassType(String name) { |
| final int className = myContext.get(name); |
| myUsages.add(UsageRepr.createClassUsage(myContext, className)); |
| myUsages.add(UsageRepr.createClassAsGenericBoundUsage(myContext, className)); |
| } |
| }; |
| |
| private Boolean myTakeIntoAccount = false; |
| |
| private final int myFileName; |
| private int myAccess; |
| private int myName; |
| private String mySuperClass; |
| private String[] myInterfaces; |
| private String mySignature; |
| |
| final Holder<String> myClassNameHolder = new Holder<String>(); |
| final Holder<String> myOuterClassName = new Holder<String>(); |
| final Holder<Boolean> myLocalClassFlag = new Holder<Boolean>(); |
| final Holder<Boolean> myAnonymousClassFlag = new Holder<Boolean>(); |
| |
| { |
| myLocalClassFlag.set(false); |
| myAnonymousClassFlag.set(false); |
| } |
| |
| private final Set<MethodRepr> myMethods = new THashSet<MethodRepr>(); |
| private final Set<FieldRepr> myFields = new THashSet<FieldRepr>(); |
| private final Set<UsageRepr.Usage> myUsages = new THashSet<UsageRepr.Usage>(); |
| private final Set<ElemType> myTargets = EnumSet.noneOf(ElemType.class); |
| private RetentionPolicy myRetentionPolicy = null; |
| |
| final Map<TypeRepr.ClassType, TIntHashSet> myAnnotationArguments = new THashMap<TypeRepr.ClassType, TIntHashSet>(); |
| final Map<TypeRepr.ClassType, Set<ElemType>> myAnnotationTargets = new THashMap<TypeRepr.ClassType, Set<ElemType>>(); |
| |
| public ClassCrawler(final int fn) { |
| super(Opcodes.ASM5); |
| myFileName = fn; |
| } |
| |
| private boolean notPrivate(final int access) { |
| return (access & Opcodes.ACC_PRIVATE) == 0; |
| } |
| |
| public Pair<ClassRepr, Set<UsageRepr.Usage>> getResult() { |
| final ClassRepr repr = |
| myTakeIntoAccount ? new ClassRepr( |
| myContext, myAccess, myFileName, myName, myContext.get(mySignature), myContext.get(mySuperClass), myInterfaces, |
| myFields, |
| myMethods, myTargets, myRetentionPolicy, myContext |
| .get(myOuterClassName.get()), myLocalClassFlag.get(), myAnonymousClassFlag.get(), myUsages) : null; |
| |
| if (repr != null) { |
| repr.updateClassUsages(myContext, myUsages); |
| } |
| |
| return Pair.create(repr, myUsages); |
| } |
| |
| @Override |
| public void visit(int version, int a, String n, String sig, String s, String[] i) { |
| myTakeIntoAccount = notPrivate(a); |
| |
| myAccess = a; |
| myName = myContext.get(n); |
| mySignature = sig; |
| mySuperClass = s; |
| myInterfaces = i; |
| |
| myClassNameHolder.set(n); |
| |
| if (mySuperClass != null) { |
| final int superclassName = myContext.get(mySuperClass); |
| myUsages.add(UsageRepr.createClassUsage(myContext, superclassName)); |
| //myUsages.add(UsageRepr.createClassExtendsUsage(myContext, superclassName)); |
| } |
| |
| if (myInterfaces != null) { |
| for (String it : myInterfaces) { |
| final int interfaceName = myContext.get(it); |
| myUsages.add(UsageRepr.createClassUsage(myContext, interfaceName)); |
| //myUsages.add(UsageRepr.createClassExtendsUsage(myContext, interfaceName)); |
| } |
| } |
| |
| processSignature(sig); |
| } |
| |
| @Override |
| public void visitEnd() { |
| for (Map.Entry<TypeRepr.ClassType, Set<ElemType>> entry : myAnnotationTargets.entrySet()) { |
| final TypeRepr.ClassType type = entry.getKey(); |
| final Set<ElemType> targets = entry.getValue(); |
| final TIntHashSet usedArguments = myAnnotationArguments.get(type); |
| |
| myUsages.add(UsageRepr.createAnnotationUsage(myContext, type, usedArguments, targets)); |
| } |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { |
| if (desc.equals("Ljava/lang/annotation/Target;")) { |
| return new AnnotationTargetCrawler(); |
| } |
| |
| if (desc.equals("Ljava/lang/annotation/Retention;")) { |
| return new AnnotationRetentionPolicyCrawler(); |
| } |
| |
| return new AnnotationCrawler( |
| (TypeRepr.ClassType)TypeRepr.getType(myContext, myContext.get(desc)), |
| (myAccess & Opcodes.ACC_ANNOTATION) > 0 ? ElemType.ANNOTATION_TYPE : ElemType.TYPE |
| ); |
| } |
| |
| @Override |
| public void visitSource(String source, String debug) { |
| } |
| |
| @Override |
| public FieldVisitor visitField(int access, String n, String desc, String signature, Object value) { |
| processSignature(signature); |
| |
| if ((access & Opcodes.ACC_SYNTHETIC) == 0) { |
| myFields.add(new FieldRepr(myContext, access, myContext.get(n), myContext.get(desc), myContext.get(signature), value)); |
| } |
| |
| return new FieldVisitor(Opcodes.ASM5) { |
| @Override |
| public AnnotationVisitor visitAnnotation(String desc, boolean visible) { |
| return new AnnotationCrawler((TypeRepr.ClassType)TypeRepr.getType(myContext, myContext.get(desc)), ElemType.FIELD); |
| } |
| }; |
| } |
| |
| @Override |
| public MethodVisitor visitMethod(final int access, final String n, final String desc, final String signature, final String[] exceptions) { |
| final Holder<Object> defaultValue = new Holder<Object>(); |
| |
| processSignature(signature); |
| |
| return new MethodVisitor(Opcodes.ASM5) { |
| @Override |
| public void visitEnd() { |
| if ((access & Opcodes.ACC_SYNTHETIC) == 0 || (access & Opcodes.ACC_BRIDGE) > 0) { |
| myMethods.add(new MethodRepr(myContext, access, myContext.get(n), myContext.get(signature), desc, exceptions, defaultValue.get())); |
| } |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotation(String desc, boolean visible) { |
| return new AnnotationCrawler( |
| (TypeRepr.ClassType)TypeRepr.getType(myContext, myContext.get(desc)), "<init>".equals(n) ? ElemType.CONSTRUCTOR : ElemType.METHOD |
| ); |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotationDefault() { |
| return new AnnotationVisitor(Opcodes.ASM5) { |
| public void visit(String name, Object value) { |
| defaultValue.set(value); |
| } |
| }; |
| } |
| |
| @Override |
| public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { |
| return new AnnotationCrawler((TypeRepr.ClassType)TypeRepr.getType(myContext, myContext.get(desc)), ElemType.PARAMETER); |
| } |
| |
| @Override |
| public void visitLdcInsn(Object cst) { |
| if (cst instanceof Type) { |
| myUsages.add(UsageRepr.createClassUsage(myContext, myContext.get(((Type)cst).getInternalName()))); |
| } |
| |
| super.visitLdcInsn(cst); |
| } |
| |
| @Override |
| public void visitMultiANewArrayInsn(String desc, int dims) { |
| final TypeRepr.ArrayType typ = (TypeRepr.ArrayType)TypeRepr.getType(myContext, myContext.get(desc)); |
| final TypeRepr.AbstractType element = typ.getDeepElementType(); |
| |
| if (element instanceof TypeRepr.ClassType) { |
| final int className = ((TypeRepr.ClassType)element).className; |
| myUsages.add(UsageRepr.createClassUsage(myContext, className)); |
| myUsages.add(UsageRepr.createClassNewUsage(myContext, className)); |
| } |
| |
| typ.updateClassUsages(myContext, myName, myUsages); |
| |
| super.visitMultiANewArrayInsn(desc, dims); |
| } |
| |
| @Override |
| public void visitLocalVariable(String n, String desc, String signature, Label start, Label end, int index) { |
| processSignature(signature); |
| TypeRepr.getType(myContext, myContext.get(desc)).updateClassUsages(myContext, myName, myUsages); |
| super.visitLocalVariable(n, desc, signature, start, end, index); |
| } |
| |
| @Override |
| public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { |
| if (type != null) { |
| TypeRepr.createClassType(myContext, myContext.get(type)).updateClassUsages(myContext, myName, myUsages); |
| } |
| |
| super.visitTryCatchBlock(start, end, handler, type); |
| } |
| |
| @Override |
| public void visitTypeInsn(int opcode, String type) { |
| final TypeRepr.AbstractType typ = type.startsWith("[") ? TypeRepr.getType(myContext, myContext.get(type)) : TypeRepr.createClassType( |
| myContext, myContext.get(type)); |
| |
| if (opcode == Opcodes.NEW) { |
| myUsages.add(UsageRepr.createClassUsage(myContext, ((TypeRepr.ClassType)typ).className)); |
| myUsages.add(UsageRepr.createClassNewUsage(myContext, ((TypeRepr.ClassType)typ).className)); |
| } |
| else if (opcode == Opcodes.ANEWARRAY) { |
| if (typ instanceof TypeRepr.ClassType) { |
| myUsages.add(UsageRepr.createClassUsage(myContext, ((TypeRepr.ClassType)typ).className)); |
| myUsages.add(UsageRepr.createClassNewUsage(myContext, ((TypeRepr.ClassType)typ).className)); |
| } |
| } |
| |
| typ.updateClassUsages(myContext, myName, myUsages); |
| |
| super.visitTypeInsn(opcode, type); |
| } |
| |
| @Override |
| public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
| final int fieldName = myContext.get(name); |
| final int fieldOwner = myContext.get(owner); |
| final int descr = myContext.get(desc); |
| |
| if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) { |
| myUsages.add(UsageRepr.createFieldAssignUsage(myContext, fieldName, fieldOwner, descr)); |
| } |
| |
| if (opcode == Opcodes.GETFIELD || opcode == Opcodes.GETSTATIC) { |
| addClassUsage(TypeRepr.getType(myContext, descr)); |
| } |
| |
| myUsages.add(UsageRepr.createFieldUsage(myContext, fieldName, fieldOwner, descr)); |
| super.visitFieldInsn(opcode, owner, name, desc); |
| } |
| |
| @Override |
| public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { |
| final int methodName = myContext.get(name); |
| final int methodOwner = myContext.get(owner); |
| |
| myUsages.add(UsageRepr.createMethodUsage(myContext, methodName, methodOwner, desc)); |
| myUsages.add(UsageRepr.createMetaMethodUsage(myContext, methodName, methodOwner, desc)); |
| addClassUsage(TypeRepr.getType(myContext, Type.getReturnType(desc))); |
| |
| super.visitMethodInsn(opcode, owner, name, desc, itf); |
| } |
| |
| private void addClassUsage(final TypeRepr.AbstractType type) { |
| TypeRepr.ClassType classType = null; |
| if (type instanceof TypeRepr.ClassType) { |
| classType = (TypeRepr.ClassType)type; |
| } |
| else if (type instanceof TypeRepr.ArrayType) { |
| final TypeRepr.AbstractType elemType = ((TypeRepr.ArrayType)type).getDeepElementType(); |
| if (elemType instanceof TypeRepr.ClassType) { |
| classType = (TypeRepr.ClassType)elemType; |
| } |
| } |
| if (classType != null) { |
| myUsages.add(UsageRepr.createClassUsage(myContext, classType.className)); |
| } |
| } |
| |
| }; |
| } |
| |
| @Override |
| public void visitInnerClass(String name, String outerName, String innerName, int access) { |
| if (outerName != null) { |
| myOuterClassName.set(outerName); |
| } |
| if (innerName == null) { |
| myAnonymousClassFlag.set(true); |
| } |
| } |
| |
| @Override |
| public void visitOuterClass(final String owner, final String name, final String desc) { |
| myOuterClassName.set(owner); |
| |
| if (name != null) { |
| myLocalClassFlag.set(true); |
| } |
| } |
| } |
| |
| public Pair<ClassRepr, Set<UsageRepr.Usage>> analyze(final int fileName, final ClassReader cr) { |
| final ClassCrawler visitor = new ClassCrawler(fileName); |
| |
| cr.accept(visitor, 0); |
| |
| return visitor.getResult(); |
| } |
| } |