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