001 package cpw.mods.fml.relauncher; 002 003 import java.io.ByteArrayOutputStream; 004 import java.io.IOException; 005 import java.io.InputStream; 006 import java.net.JarURLConnection; 007 import java.net.URL; 008 import java.net.URLClassLoader; 009 import java.net.URLConnection; 010 import java.security.CodeSigner; 011 import java.security.CodeSource; 012 import java.util.ArrayList; 013 import java.util.Arrays; 014 import java.util.Collections; 015 import java.util.HashMap; 016 import java.util.HashSet; 017 import java.util.List; 018 import java.util.Map; 019 import java.util.Set; 020 import java.util.jar.Attributes.Name; 021 import java.util.jar.Attributes; 022 import java.util.jar.JarEntry; 023 import java.util.jar.JarFile; 024 import java.util.jar.Manifest; 025 import java.util.logging.Level; 026 027 import cpw.mods.fml.common.FMLLog; 028 029 public class RelaunchClassLoader extends URLClassLoader 030 { 031 private List<URL> sources; 032 private ClassLoader parent; 033 034 private List<IClassTransformer> transformers; 035 private Map<String, Class> cachedClasses; 036 private Set<String> invalidClasses; 037 038 private Set<String> classLoaderExceptions = new HashSet<String>(); 039 private Set<String> transformerExceptions = new HashSet<String>(); 040 private Map<Package,Manifest> packageManifests = new HashMap<Package,Manifest>(); 041 042 private static Manifest EMPTY = new Manifest(); 043 044 public RelaunchClassLoader(URL[] sources) 045 { 046 super(sources, null); 047 this.sources = new ArrayList<URL>(Arrays.asList(sources)); 048 this.parent = getClass().getClassLoader(); 049 this.cachedClasses = new HashMap<String,Class>(1000); 050 this.invalidClasses = new HashSet<String>(1000); 051 this.transformers = new ArrayList<IClassTransformer>(2); 052 // ReflectionHelper.setPrivateValue(ClassLoader.class, null, this, "scl"); 053 Thread.currentThread().setContextClassLoader(this); 054 055 // standard classloader exclusions 056 addClassLoaderExclusion("java."); 057 addClassLoaderExclusion("sun."); 058 addClassLoaderExclusion("org.lwjgl."); 059 addClassLoaderExclusion("cpw.mods.fml.relauncher."); 060 addClassLoaderExclusion("net.minecraftforge.classloading."); 061 062 // standard transformer exclusions 063 addTransformerExclusion("javax."); 064 addTransformerExclusion("org.objectweb.asm."); 065 addTransformerExclusion("com.google.common."); 066 } 067 068 public void registerTransformer(String transformerClassName) 069 { 070 try 071 { 072 transformers.add((IClassTransformer) loadClass(transformerClassName).newInstance()); 073 } 074 catch (Exception e) 075 { 076 FMLRelaunchLog.log(Level.SEVERE, e, "A critical problem occured registering the ASM transformer class %s", transformerClassName); 077 } 078 } 079 @Override 080 public Class<?> findClass(String name) throws ClassNotFoundException 081 { 082 if (invalidClasses.contains(name)) 083 { 084 throw new ClassNotFoundException(name); 085 } 086 for (String st : classLoaderExceptions) 087 { 088 if (name.startsWith(st)) 089 { 090 return parent.loadClass(name); 091 } 092 } 093 094 if (cachedClasses.containsKey(name)) 095 { 096 return cachedClasses.get(name); 097 } 098 099 for (String st : transformerExceptions) 100 { 101 if (name.startsWith(st)) 102 { 103 try 104 { 105 Class<?> cl = super.findClass(name); 106 cachedClasses.put(name, cl); 107 return cl; 108 } 109 catch (ClassNotFoundException e) 110 { 111 invalidClasses.add(name); 112 throw e; 113 } 114 } 115 } 116 117 try 118 { 119 CodeSigner[] signers = null; 120 int lastDot = name.lastIndexOf('.'); 121 String pkgname = lastDot == -1 ? "" : name.substring(0, lastDot); 122 String fName = name.replace('.', '/').concat(".class"); 123 String pkgPath = pkgname.replace('.', '/'); 124 URLConnection urlConnection = findCodeSourceConnectionFor(fName); 125 if (urlConnection instanceof JarURLConnection && lastDot > -1) 126 { 127 JarURLConnection jarUrlConn = (JarURLConnection)urlConnection; 128 JarFile jf = jarUrlConn.getJarFile(); 129 if (jf != null && jf.getManifest() != null) 130 { 131 Manifest mf = jf.getManifest(); 132 JarEntry ent = jf.getJarEntry(fName); 133 Package pkg = getPackage(pkgname); 134 getClassBytes(name); 135 signers = ent.getCodeSigners(); 136 if (pkg == null) 137 { 138 pkg = definePackage(pkgname, mf, jarUrlConn.getJarFileURL()); 139 packageManifests.put(pkg, mf); 140 } 141 else 142 { 143 if (pkg.isSealed() && !pkg.isSealed(jarUrlConn.getJarFileURL())) 144 { 145 FMLLog.severe("The jar file %s is trying to seal already secured path %s", jf.getName(), pkgname); 146 } 147 else if (isSealed(pkgname, mf)) 148 { 149 FMLLog.severe("The jar file %s has a security seal for path %s, but that path is defined and not secure", jf.getName(), pkgname); 150 } 151 } 152 } 153 } 154 else if (lastDot > -1) 155 { 156 Package pkg = getPackage(pkgname); 157 if (pkg == null) 158 { 159 pkg = definePackage(pkgname, null, null, null, null, null, null, null); 160 packageManifests.put(pkg, EMPTY); 161 } 162 else if (pkg.isSealed()) 163 { 164 FMLLog.severe("The URL %s is defining elements for sealed path %s", urlConnection.getURL(), pkgname); 165 } 166 } 167 byte[] basicClass = getClassBytes(name); 168 byte[] transformedClass = runTransformers(name, basicClass); 169 Class<?> cl = defineClass(name, transformedClass, 0, transformedClass.length, new CodeSource(urlConnection.getURL(), signers)); 170 cachedClasses.put(name, cl); 171 return cl; 172 } 173 catch (Throwable e) 174 { 175 invalidClasses.add(name); 176 throw new ClassNotFoundException(name, e); 177 } 178 } 179 180 private boolean isSealed(String path, Manifest man) 181 { 182 Attributes attr = man.getAttributes(path); 183 String sealed = null; 184 if (attr != null) { 185 sealed = attr.getValue(Name.SEALED); 186 } 187 if (sealed == null) { 188 if ((attr = man.getMainAttributes()) != null) { 189 sealed = attr.getValue(Name.SEALED); 190 } 191 } 192 return "true".equalsIgnoreCase(sealed); 193 } 194 195 private URLConnection findCodeSourceConnectionFor(String name) 196 { 197 URL res = findResource(name); 198 if (res != null) 199 { 200 try 201 { 202 return res.openConnection(); 203 } 204 catch (IOException e) 205 { 206 throw new RuntimeException(e); 207 } 208 } 209 else 210 { 211 return null; 212 } 213 } 214 215 private byte[] runTransformers(String name, byte[] basicClass) 216 { 217 for (IClassTransformer transformer : transformers) 218 { 219 basicClass = transformer.transform(name, basicClass); 220 } 221 return basicClass; 222 } 223 224 @Override 225 public void addURL(URL url) 226 { 227 super.addURL(url); 228 sources.add(url); 229 } 230 231 public List<URL> getSources() 232 { 233 return sources; 234 } 235 236 237 private byte[] readFully(InputStream stream) 238 { 239 try 240 { 241 ByteArrayOutputStream bos = new ByteArrayOutputStream(stream.available()); 242 int r; 243 while ((r = stream.read()) != -1) 244 { 245 bos.write(r); 246 } 247 248 return bos.toByteArray(); 249 } 250 catch (Throwable t) 251 { 252 /// HMMM 253 return new byte[0]; 254 } 255 } 256 257 public List<IClassTransformer> getTransformers() 258 { 259 return Collections.unmodifiableList(transformers); 260 } 261 262 private void addClassLoaderExclusion(String toExclude) 263 { 264 classLoaderExceptions.add(toExclude); 265 } 266 267 void addTransformerExclusion(String toExclude) 268 { 269 transformerExceptions.add(toExclude); 270 } 271 272 public byte[] getClassBytes(String name) throws IOException 273 { 274 InputStream classStream = null; 275 try 276 { 277 URL classResource = findResource(name.replace('.', '/').concat(".class")); 278 if (classResource == null) 279 { 280 return null; 281 } 282 classStream = classResource.openStream(); 283 return readFully(classStream); 284 } 285 finally 286 { 287 if (classStream != null) 288 { 289 try 290 { 291 classStream.close(); 292 } 293 catch (IOException e) 294 { 295 // Swallow the close exception 296 } 297 } 298 } 299 } 300 }