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}