001package io.ebean.enhance.asm;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.util.ArrayList;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.List;
009import java.util.Map;
010import java.util.Set;
011
012import io.ebean.enhance.common.CommonSuperUnresolved;
013
014/**
015 * ClassWriter without class loading. Fixes problems on dynamic enhancement mentioned here:
016 * https://github.com/ebean-orm/ebean-agent/issues/59
017 *
018 * Idea taken from here:
019 *
020 * https://github.com/zygote1984/AspectualAdapters/blob/master/ALIA4J-NOIRIn-all/src/org/alia4j/noirin/transform/ClassWriterWithoutClassLoading.java
021 *
022 * @author praml
023 */
024public class ClassWriterWithoutClassLoading extends ClassWriter {
025
026  private final Map<String, Set<String>> type2instanceOfs = new HashMap<>();
027
028  private final Map<String, String> type2superclass = new HashMap<>();
029
030  private final Map<String, Boolean> type2isInterface = new HashMap<>();
031
032  private final ClassLoader classLoader;
033
034  private List<CommonSuperUnresolved> unresolved = new ArrayList<>();
035
036  public ClassWriterWithoutClassLoading(ClassReader classReader, int flags, ClassLoader classLoader) {
037    super(classReader, flags);
038    this.classLoader = classLoader;
039  }
040
041  public ClassWriterWithoutClassLoading(int flags, ClassLoader classLoader) {
042    super(flags);
043    this.classLoader = classLoader;
044  }
045
046  public List<CommonSuperUnresolved> getUnresolved() {
047    return unresolved;
048  }
049
050  /**
051  * Returns the common super type of the two given types.
052  *
053  * @param type1 the internal name of a class.
054  * @param type2 the internal name of another class.
055  * @return the internal name of the common super class of the two given classes.
056  */
057  @Override
058  protected String getCommonSuperClass(final String type1, final String type2) {
059    try {
060      if (getInstanceOfs(type2).contains(type1)) {
061        return type1;
062      }
063      if (getInstanceOfs(type1).contains(type2)) {
064        return type2;
065      }
066      if (isInterface(type1) || isInterface(type2)) {
067        return "java/lang/Object";
068      } else {
069        String type = type1;
070        do {
071          type = getSuperclass(type);
072        } while (!getInstanceOfs(type2).contains(type));
073        return type;
074      }
075    } catch (Exception e) {
076      unresolved.add(new CommonSuperUnresolved(type1, type2, e.toString()));
077      return "java/lang/Object";
078    }
079  }
080
081  private String getSuperclass(String type) {
082    if (!type2superclass.containsKey(type)) {
083      initializeTypeHierarchyFor(type);
084    }
085    return type2superclass.get(type);
086  }
087
088  private boolean isInterface(String type) {
089    if (!type2isInterface.containsKey(type)) {
090      initializeTypeHierarchyFor(type);
091    }
092    return type2isInterface.get(type);
093  }
094
095  private Set<String> getInstanceOfs(String type) {
096    if (!type2instanceOfs.containsKey(type)) {
097      initializeTypeHierarchyFor(type);
098    }
099    return type2instanceOfs.get(type);
100  }
101
102  /**
103  * Here we read the class at bytecode-level.
104  */
105  private void initializeTypeHierarchyFor(final String internalTypeName) {
106    if (classLoader == null) {
107      // Bug in Zulu JDK for jdk classes (which we should skip anyway)
108      throw new IllegalStateException("ClassLoader is null?");
109    }
110    try (InputStream classBytes = classLoader.getResourceAsStream(internalTypeName + ".class")){
111      ClassReader classReader = new ClassReader(classBytes);
112      classReader.accept(new ClassVisitor(Opcodes.ASM7) {
113
114        @Override
115        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
116          super.visit(version, access, name, signature, superName, interfaces);
117          type2superclass.put(internalTypeName, superName);
118          type2isInterface.put(internalTypeName, (access & Opcodes.ACC_INTERFACE) > 0);
119
120          Set<String> instanceOfs = new HashSet<>();
121          instanceOfs.add(internalTypeName); // we are instance of ourself
122          if (superName != null) {
123            instanceOfs.add(superName);
124            instanceOfs.addAll(getInstanceOfs(superName));
125          }
126          for (String superInterface : interfaces) {
127            instanceOfs.add(superInterface);
128            instanceOfs.addAll(getInstanceOfs(superInterface));
129          }
130          type2instanceOfs.put(internalTypeName, instanceOfs);
131        }
132      }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
133    } catch (IOException e) {
134      throw new IllegalArgumentException(e);
135    }
136  }
137
138}