blob: 01013e05186adf99c522b21d928f572884e4a4b6 [file] [log] [blame]
/*
* 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();
}
}