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