001/* 002 * Forge Mod Loader 003 * Copyright (c) 2012-2013 cpw. 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser Public License v2.1 006 * which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 008 * 009 * Contributors: 010 * cpw - implementation 011 */ 012 013package cpw.mods.fml.relauncher; 014 015import java.io.ByteArrayOutputStream; 016import java.io.IOException; 017import java.io.InputStream; 018import java.net.JarURLConnection; 019import java.net.URL; 020import java.net.URLClassLoader; 021import java.net.URLConnection; 022import java.security.CodeSigner; 023import java.security.CodeSource; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Locale; 031import java.util.Map; 032import java.util.Set; 033import java.util.jar.Attributes.Name; 034import java.util.jar.Attributes; 035import java.util.jar.JarEntry; 036import java.util.jar.JarFile; 037import java.util.jar.Manifest; 038import java.util.logging.Level; 039 040import cpw.mods.fml.common.FMLLog; 041 042public class RelaunchClassLoader extends URLClassLoader 043{ 044 private List<URL> sources; 045 private ClassLoader parent; 046 047 private List<IClassTransformer> transformers; 048 private Map<String, Class> cachedClasses; 049 private Set<String> invalidClasses; 050 051 private Set<String> classLoaderExceptions = new HashSet<String>(); 052 private Set<String> transformerExceptions = new HashSet<String>(); 053 private Map<Package,Manifest> packageManifests = new HashMap<Package,Manifest>(); 054 private IClassNameTransformer renameTransformer; 055 056 private static Manifest EMPTY = new Manifest(); 057 058 private ThreadLocal<byte[]> loadBuffer = new ThreadLocal<byte[]>(); 059 060 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"}; 061 062 private static final boolean DEBUG_CLASSLOADING = Boolean.parseBoolean(System.getProperty("fml.debugClassLoading", "false")); 063 064 public RelaunchClassLoader(URL[] sources) 065 { 066 super(sources, null); 067 this.sources = new ArrayList<URL>(Arrays.asList(sources)); 068 this.parent = getClass().getClassLoader(); 069 this.cachedClasses = new HashMap<String,Class>(1000); 070 this.invalidClasses = new HashSet<String>(1000); 071 this.transformers = new ArrayList<IClassTransformer>(2); 072// ReflectionHelper.setPrivateValue(ClassLoader.class, null, this, "scl"); 073 Thread.currentThread().setContextClassLoader(this); 074 075 // standard classloader exclusions 076 addClassLoaderExclusion("java."); 077 addClassLoaderExclusion("sun."); 078 addClassLoaderExclusion("org.lwjgl."); 079 addClassLoaderExclusion("cpw.mods.fml.relauncher."); 080 addClassLoaderExclusion("net.minecraftforge.classloading."); 081 082 // standard transformer exclusions 083 addTransformerExclusion("javax."); 084 addTransformerExclusion("argo."); 085 addTransformerExclusion("org.objectweb.asm."); 086 addTransformerExclusion("com.google.common."); 087 addTransformerExclusion("org.bouncycastle."); 088 addTransformerExclusion("cpw.mods.fml.common.asm.transformers.deobf."); 089 } 090 091 public void registerTransformer(String transformerClassName) 092 { 093 try 094 { 095 IClassTransformer transformer = (IClassTransformer) loadClass(transformerClassName).newInstance(); 096 transformers.add(transformer); 097 if (transformer instanceof IClassNameTransformer && renameTransformer == null) 098 { 099 renameTransformer = (IClassNameTransformer) transformer; 100 } 101 } 102 catch (Exception e) 103 { 104 FMLRelaunchLog.log(Level.SEVERE, e, "A critical problem occured registering the ASM transformer class %s", transformerClassName); 105 } 106 } 107 @Override 108 public Class<?> findClass(String name) throws ClassNotFoundException 109 { 110 if (invalidClasses.contains(name)) 111 { 112 throw new ClassNotFoundException(name); 113 } 114 for (String st : classLoaderExceptions) 115 { 116 if (name.startsWith(st)) 117 { 118 return parent.loadClass(name); 119 } 120 } 121 122 if (cachedClasses.containsKey(name)) 123 { 124 return cachedClasses.get(name); 125 } 126 127 for (String st : transformerExceptions) 128 { 129 if (name.startsWith(st)) 130 { 131 try 132 { 133 Class<?> cl = super.findClass(name); 134 cachedClasses.put(name, cl); 135 return cl; 136 } 137 catch (ClassNotFoundException e) 138 { 139 invalidClasses.add(name); 140 throw e; 141 } 142 } 143 } 144 145 try 146 { 147 CodeSigner[] signers = null; 148 String transformedName = transformName(name); 149 String untransformedName = untransformName(name); 150 int lastDot = untransformedName.lastIndexOf('.'); 151 String pkgname = lastDot == -1 ? "" : untransformedName.substring(0, lastDot); 152 String fName = untransformedName.replace('.', '/').concat(".class"); 153 String pkgPath = pkgname.replace('.', '/'); 154 URLConnection urlConnection = findCodeSourceConnectionFor(fName); 155 if (urlConnection instanceof JarURLConnection && lastDot > -1 && !untransformedName.startsWith("net.minecraft.")) 156 { 157 JarURLConnection jarUrlConn = (JarURLConnection)urlConnection; 158 JarFile jf = jarUrlConn.getJarFile(); 159 if (jf != null && jf.getManifest() != null) 160 { 161 Manifest mf = jf.getManifest(); 162 JarEntry ent = jf.getJarEntry(fName); 163 Package pkg = getPackage(pkgname); 164 getClassBytes(untransformedName); 165 signers = ent.getCodeSigners(); 166 if (pkg == null) 167 { 168 pkg = definePackage(pkgname, mf, jarUrlConn.getJarFileURL()); 169 packageManifests.put(pkg, mf); 170 } 171 else 172 { 173 if (pkg.isSealed() && !pkg.isSealed(jarUrlConn.getJarFileURL())) 174 { 175 FMLLog.severe("The jar file %s is trying to seal already secured path %s", jf.getName(), pkgname); 176 } 177 else if (isSealed(pkgname, mf)) 178 { 179 FMLLog.severe("The jar file %s has a security seal for path %s, but that path is defined and not secure", jf.getName(), pkgname); 180 } 181 } 182 } 183 } 184 else if (lastDot > -1 && !untransformedName.startsWith("net.minecraft.")) 185 { 186 Package pkg = getPackage(pkgname); 187 if (pkg == null) 188 { 189 pkg = definePackage(pkgname, null, null, null, null, null, null, null); 190 packageManifests.put(pkg, EMPTY); 191 } 192 else if (pkg.isSealed()) 193 { 194 FMLLog.severe("The URL %s is defining elements for sealed path %s", urlConnection.getURL(), pkgname); 195 } 196 } 197 byte[] basicClass = getClassBytes(untransformedName); 198 byte[] transformedClass = runTransformers(untransformedName, transformedName, basicClass); 199 Class<?> cl = defineClass(transformedName, transformedClass, 0, transformedClass.length, new CodeSource(urlConnection.getURL(), signers)); 200 cachedClasses.put(transformedName, cl); 201 return cl; 202 } 203 catch (Throwable e) 204 { 205 invalidClasses.add(name); 206 if (DEBUG_CLASSLOADING) 207 { 208 FMLLog.log(Level.FINEST, e, "Exception encountered attempting classloading of %s", name); 209 } 210 throw new ClassNotFoundException(name, e); 211 } 212 } 213 214 private String untransformName(String name) 215 { 216 if (renameTransformer != null) 217 { 218 return renameTransformer.unmapClassName(name); 219 } 220 else 221 { 222 return name; 223 } 224 } 225 226 private String transformName(String name) 227 { 228 if (renameTransformer != null) 229 { 230 return renameTransformer.remapClassName(name); 231 } 232 else 233 { 234 return name; 235 } 236 } 237 238 private boolean isSealed(String path, Manifest man) 239 { 240 Attributes attr = man.getAttributes(path); 241 String sealed = null; 242 if (attr != null) { 243 sealed = attr.getValue(Name.SEALED); 244 } 245 if (sealed == null) { 246 if ((attr = man.getMainAttributes()) != null) { 247 sealed = attr.getValue(Name.SEALED); 248 } 249 } 250 return "true".equalsIgnoreCase(sealed); 251 } 252 253 private URLConnection findCodeSourceConnectionFor(String name) 254 { 255 URL res = findResource(name); 256 if (res != null) 257 { 258 try 259 { 260 return res.openConnection(); 261 } 262 catch (IOException e) 263 { 264 throw new RuntimeException(e); 265 } 266 } 267 else 268 { 269 return null; 270 } 271 } 272 273 private byte[] runTransformers(String name, String transformedName, byte[] basicClass) 274 { 275 for (IClassTransformer transformer : transformers) 276 { 277 basicClass = transformer.transform(name, transformedName, basicClass); 278 } 279 return basicClass; 280 } 281 282 @Override 283 public void addURL(URL url) 284 { 285 super.addURL(url); 286 sources.add(url); 287 } 288 289 public List<URL> getSources() 290 { 291 return sources; 292 } 293 294 295 private byte[] readFully(InputStream stream) 296 { 297 try 298 { 299 byte[] buf = loadBuffer.get(); 300 if (buf == null) 301 { 302 loadBuffer.set(new byte[1 << 12]); 303 buf = loadBuffer.get(); 304 } 305 306 int r, totalLength = 0; 307 while ((r = stream.read(buf, totalLength, buf.length - totalLength)) != -1) 308 { 309 totalLength += r; 310 if (totalLength >= buf.length - 1) 311 { 312 byte[] oldbuf = buf; 313 buf = new byte[ oldbuf.length + (1 << 12 )]; 314 System.arraycopy(oldbuf, 0, buf, 0, oldbuf.length); 315 } 316 } 317 318 byte[] result = new byte[totalLength]; 319 System.arraycopy(buf, 0, result, 0, totalLength); 320 return result; 321 } 322 catch (Throwable t) 323 { 324 FMLRelaunchLog.log(Level.WARNING, t, "Problem loading class"); 325 return new byte[0]; 326 } 327 } 328 329 public List<IClassTransformer> getTransformers() 330 { 331 return Collections.unmodifiableList(transformers); 332 } 333 334 private void addClassLoaderExclusion(String toExclude) 335 { 336 classLoaderExceptions.add(toExclude); 337 } 338 339 void addTransformerExclusion(String toExclude) 340 { 341 transformerExceptions.add(toExclude); 342 } 343 344 public byte[] getClassBytes(String name) throws IOException 345 { 346 if (name.indexOf('.') == -1) 347 { 348 for (String res : RESERVED) 349 { 350 if (name.toUpperCase(Locale.ENGLISH).startsWith(res)) 351 { 352 byte[] data = getClassBytes("_" + name); 353 if (data != null) 354 { 355 return data; 356 } 357 } 358 } 359 } 360 361 InputStream classStream = null; 362 try 363 { 364 URL classResource = findResource(name.replace('.', '/').concat(".class")); 365 if (classResource == null) 366 { 367 if (DEBUG_CLASSLOADING) 368 { 369 FMLLog.finest("Failed to find class resource %s", name.replace('.', '/').concat(".class")); 370 } 371 return null; 372 } 373 classStream = classResource.openStream(); 374 if (DEBUG_CLASSLOADING) 375 { 376 FMLLog.finest("Loading class %s from resource %s", name, classResource.toString()); 377 } 378 return readFully(classStream); 379 } 380 finally 381 { 382 if (classStream != null) 383 { 384 try 385 { 386 classStream.close(); 387 } 388 catch (IOException e) 389 { 390 // Swallow the close exception 391 } 392 } 393 } 394 } 395}