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 }