    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}