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