Merge pull request #597 from RainWarrior/blame

Added blaming of coremods and mods not targetting Java 6
This commit is contained in:
Fry 2015-02-13 08:05:20 +03:00
commit c85aa9351a
10 changed files with 525 additions and 8 deletions

View file

@ -8,3 +8,12 @@
}
public String func_71501_a()
@@ -220,6 +221,8 @@
{
StringBuilder stringbuilder = new StringBuilder();
stringbuilder.append("---- Minecraft Crash Report ----\n");
+ net.minecraftforge.fml.common.asm.transformers.BlamingTransformer.onCrash(stringbuilder);
+ net.minecraftforge.fml.relauncher.CoreModManager.onCrash(stringbuilder);
stringbuilder.append("// ");
stringbuilder.append(func_71503_h());
stringbuilder.append("\n\n");

View file

@ -55,6 +55,7 @@ import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent.Phase;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.relauncher.CoreModManager;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.server.FMLServerHandler;
@ -107,6 +108,28 @@ public class FMLCommonHandler
private WeakReference<SaveHandler> handlerToCheck;
private EventBus eventBus = new EventBus();
private volatile CountDownLatch exitLatch = null;
private FMLCommonHandler()
{
registerCrashCallable(new ICrashCallable()
{
public String call() throws Exception
{
StringBuilder builder = new StringBuilder();
Joiner joiner = Joiner.on("\n ");
for(String coreMod : CoreModManager.getTransformers().keySet())
{
builder.append("\n" + coreMod + "\n ").append(joiner.join(CoreModManager.getTransformers().get(coreMod)));
}
return builder.toString();
}
public String getLabel()
{
return "Loaded coremods (and transformers)";
}
});
}
/**
* The FML event bus. Subscribe here for FML related events
*

View file

@ -27,6 +27,7 @@ import java.util.Set;
import net.minecraftforge.fml.common.Mod.Instance;
import net.minecraftforge.fml.common.Mod.Metadata;
import net.minecraftforge.fml.common.asm.transformers.BlamingTransformer;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.ModCandidate;
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData;
@ -416,6 +417,7 @@ public class FMLModContainer implements ModContainer
{
try
{
BlamingTransformer.addClasses(getModId(), candidate.getClassList());
ModClassLoader modClassLoader = event.getModClassLoader();
modClassLoader.addFile(source);
modClassLoader.clearNegativeCacheFor(candidate.getClassList());

View file

@ -14,19 +14,21 @@ package net.minecraftforge.fml.common;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.logging.log4j.Level;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.common.asm.transformers.ModAPITransformer;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import org.apache.logging.log4j.Level;
import com.google.common.collect.ImmutableList;
/**
@ -59,15 +61,18 @@ public class ModClassLoader extends URLClassLoader
}
public File[] getParentSources() {
List<URL> urls=mainClassLoader.getSources();
File[] sources=new File[urls.size()];
try
{
for (int i = 0; i<urls.size(); i++)
List<File> files=new ArrayList<File>();
for(URL url : mainClassLoader.getSources())
{
sources[i]=new File(urls.get(i).toURI());
URI uri = url.toURI();
if(uri.getScheme() == "file")
{
files.add(new File(uri));
}
}
return sources;
return files.toArray(new File[]{});
}
catch (URISyntaxException e)
{

View file

@ -0,0 +1,263 @@
package net.minecraftforge.fml.common.asm;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.security.Permission;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.LaunchClassLoader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
public class ASMTransformerWrapper
{
private static final Map<String, String> wrapperModMap = Maps.newHashMap();
private static final Map<String, String> wrapperParentMap = Maps.newHashMap();
private static final LoadingCache<String, byte[]> wrapperCache = CacheBuilder.newBuilder()
.maximumSize(30)
.weakValues()
.build(new CacheLoader<String, byte[]>()
{
public byte[] load(String file) throws Exception
{
return makeWrapper(file);
}
});
private static final URL asmGenRoot;
private static boolean injected = false;
static
{
try
{
asmGenRoot = new URL("asmgen", null, -1, "/", new ASMGenHandler());
}
catch(MalformedURLException e)
{
throw new RuntimeException(e);
}
}
private static class ASMGenHandler extends URLStreamHandler
{
protected URLConnection openConnection(URL url) throws IOException
{
String file = url.getFile();
if(file.equals("/"))
{
return new URLConnection(url)
{
public void connect() throws IOException
{
throw new UnsupportedOperationException();
}
};
}
if(!file.startsWith("/")) throw new RuntimeException("Malformed URL: " + url);
file = file.substring(1);
if(wrapperModMap.containsKey(file))
{
return new ASMGenConnection(url, file);
}
return null;
}
}
private static class ASMGenConnection extends URLConnection
{
private final String file;
protected ASMGenConnection(URL url, String file)
{
super(url);
this.file = file;
}
public void connect() throws IOException
{
throw new UnsupportedOperationException();
}
@Override
public InputStream getInputStream()
{
try
{
return new ByteArrayInputStream(wrapperCache.get(file));
}
catch (ExecutionException e)
{
throw new RuntimeException(e);
}
}
@Override
public Permission getPermission()
{
return null;
}
}
public static String getTransformerWrapper(LaunchClassLoader launchLoader, String parentClass, String coreMod)
{
if(!injected)
{
injected = true;
launchLoader.addURL(asmGenRoot);
}
String name = getWrapperName(parentClass);
String fileName = name.replace('.', '/') + ".class";
wrapperModMap.put(fileName, coreMod);
wrapperParentMap.put(fileName, parentClass);
return name;
}
private static byte[] makeWrapper(String fileName)
{
if(!wrapperModMap.containsKey(fileName) || !wrapperParentMap.containsKey(fileName) || !fileName.endsWith(".class"))
{
throw new IllegalArgumentException("makeWrapper called with strange argument: " + fileName);
}
String name = fileName.substring(0, fileName.length() - ".class".length());
try
{
Type wrapper = Type.getType(TransformerWrapper.class);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
writer.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, name, null, wrapper.getInternalName(), null);
Method m = Method.getMethod("void <init> ()");
GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, m, null, null, writer);
mg.loadThis();
mg.invokeConstructor(wrapper, m);
mg.returnValue();
mg.endMethod();
m = Method.getMethod("java.lang.String getParentClass ()");
mg = new GeneratorAdapter(Opcodes.ACC_PROTECTED, m, null, null, writer);
mg.push(wrapperParentMap.get(fileName));
mg.returnValue();
mg.endMethod();
m = Method.getMethod("java.lang.String getCoreMod ()");
mg = new GeneratorAdapter(Opcodes.ACC_PROTECTED, m, null, null, writer);
mg.push(wrapperModMap.get(fileName));
mg.returnValue();
mg.endMethod();
writer.visitEnd();
return writer.toByteArray();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
private static String getWrapperName(String parentClass)
{
return parentClass + "_$Wrapper";
}
private static class WrapperVisitor extends ClassVisitor
{
private final String name;
private final String parentClass;
public WrapperVisitor(ClassVisitor cv, String name, String parentClass)
{
super(Opcodes.ASM5, cv);
this.name = name.replace('.', '/');
this.parentClass = parentClass;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
super.visit(version, access, this.name, signature, superName, interfaces);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
{
if(name.equals("parentClass"))
{
return super.visitField(access, name, desc, signature, parentClass);
}
return super.visitField(access, name, desc, signature, value);
}
}
public static abstract class TransformerWrapper implements IClassTransformer
{
private final IClassTransformer parent;
public TransformerWrapper()
{
try
{
this.parent = (IClassTransformer)this.getClass().getClassLoader().loadClass(getParentClass()).newInstance();
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
public byte[] transform(String name, String transformedName, byte[] basicClass)
{
try
{
return parent.transform(name, transformedName, basicClass);
}
catch(Throwable e)
{
throw new TransformerException("Exception in class transformer " + parent + " from coremod " + getCoreMod(), e);
}
}
@Override
public String toString()
{
return "TransformerWrapper(" + getParentClass() + ", " + getCoreMod() + ")";
}
protected abstract String getParentClass();
protected abstract String getCoreMod();
}
static class TransformerException extends RuntimeException
{
public TransformerException(String message, Throwable cause)
{
super(message, cause);
}
}
}

View file

@ -0,0 +1,119 @@
package net.minecraftforge.fml.common.asm.transformers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraftforge.fml.common.FMLLog;
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import com.google.common.collect.ImmutableSet;
public class BlamingTransformer implements IClassTransformer
{
private static final Map<String, String> classMap = new HashMap<String, String>();
private static final Set<String> naughtyMods = new HashSet<String>();
private static final Set<String> naughtyClasses = new TreeSet<String>();
private static final Set<String> orphanNaughtyClasses = new HashSet();
@Override
public byte[] transform(String name, String transformedName, byte[] bytes)
{
if (bytes == null) { return null; }
ClassReader classReader = new ClassReader(bytes);
VersionVisitor visitor = new VersionVisitor();
classReader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
return bytes;
}
public static void blame(String modId, String cls)
{
naughtyClasses.add(cls);
naughtyMods.add(modId);
FMLLog.severe("Unsupported class format in mod %s: class %s", modId, cls);
}
public static class VersionVisitor extends ClassVisitor
{
public VersionVisitor()
{
super(Opcodes.ASM5);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
if( (version == Opcodes.V1_8 && !SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_8)) ||
(version == Opcodes.V1_7 && !SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_7)) )
{
if(classMap.containsKey(name)) blame(classMap.get(name), name);
else orphanNaughtyClasses.add(name);
}
}
}
private static void checkPendingNaughty()
{
ImmutableSet.Builder<String> toRemove = ImmutableSet.builder();
for(String cls : orphanNaughtyClasses)
{
if(classMap.containsKey(cls))
{
String modId = classMap.get(cls);
blame(modId, cls);
toRemove.add(cls);
}
}
orphanNaughtyClasses.removeAll(toRemove.build());
}
public static void addClasses(String modId, Set<String> classList)
{
for(String cls : classList)
{
classMap.put(cls, modId);
}
checkPendingNaughty();
}
public static void onCrash(StringBuilder builder)
{
checkPendingNaughty();
if(!naughtyClasses.isEmpty())
{
builder.append("\n*** ATTENTION: detected classes with unsupported format ***\n");
builder.append("*** DO NOT SUBMIT THIS CRASH REPORT TO FORGE ***\n\n");
if(!naughtyMods.isEmpty())
{
builder.append("Contact authors of the following mods: \n");
for(String modId : naughtyMods)
{
builder.append(" ").append(modId).append("\n");
}
}
if(!orphanNaughtyClasses.isEmpty())
{
builder.append("Unidentified unsupported classes: \n");
for(String cls : orphanNaughtyClasses)
{
builder.append(" ").append(cls).append("\n");
}
}
builder.append('\n');
}
}
}

View file

@ -25,6 +25,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
@ -32,6 +33,7 @@ import net.minecraft.launchwrapper.ITweaker;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.asm.ASMTransformerWrapper;
import net.minecraftforge.fml.common.asm.transformers.ModAccessTransformer;
import net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker;
import net.minecraftforge.fml.common.launcher.FMLTweaker;
@ -50,6 +52,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.ObjectArrays;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
public class CoreModManager {
@ -58,12 +61,22 @@ public class CoreModManager {
private static final Attributes.Name MODSIDE = new Attributes.Name("ModSide");
private static String[] rootPlugins = { "net.minecraftforge.fml.relauncher.FMLCorePlugin", "net.minecraftforge.classloading.FMLForgePlugin" };
private static List<String> loadedCoremods = Lists.newArrayList();
private static Map<String, List<String>> transformers = Maps.newHashMap();
private static List<FMLPluginWrapper> loadPlugins;
private static boolean deobfuscatedEnvironment;
private static FMLTweaker tweaker;
private static File mcDir;
private static List<String> reparsedCoremods = Lists.newArrayList();
private static List<String> accessTransformers = Lists.newArrayList();
private static Set<String> rootNames = Sets.newHashSet();
static
{
for(String cls : rootPlugins)
{
rootNames.add(cls.substring(cls.lastIndexOf('.') + 1));
}
}
private static class FMLPluginWrapper implements ITweaker {
public final String name;
@ -98,10 +111,19 @@ public class CoreModManager {
public void injectIntoClassLoader(LaunchClassLoader classLoader)
{
FMLRelaunchLog.fine("Injecting coremod %s {%s} class transformers", name, coreModInstance.getClass().getName());
List<String> ts = Lists.newArrayList();
if (coreModInstance.getASMTransformerClass() != null) for (String transformer : coreModInstance.getASMTransformerClass())
{
FMLRelaunchLog.finer("Registering transformer %s", transformer);
classLoader.registerTransformer(transformer);
classLoader.registerTransformer(ASMTransformerWrapper.getTransformerWrapper(classLoader, transformer, name));
ts.add(transformer);
}
if(!rootNames.contains(name))
{
String loc;
if(location == null) loc = "unknown";
else loc = location.getName();
transformers.put(name + " (" + loc + ")", ts);
}
FMLRelaunchLog.fine("Injection complete");
@ -405,6 +427,11 @@ public class CoreModManager {
return loadedCoremods;
}
public static Map<String, List<String>> getTransformers()
{
return transformers;
}
public static List<String> getReparseableCoremods()
{
return reparsedCoremods;
@ -609,4 +636,17 @@ public class CoreModManager {
{
return accessTransformers;
}
public static void onCrash(StringBuilder builder)
{
if(!loadedCoremods.isEmpty() || !reparsedCoremods.isEmpty())
{
builder.append("\nWARNING: coremods are present:\n");
for(String coreMod : transformers.keySet())
{
builder.append(" ").append(coreMod).append('\n');
}
builder.append("Contact their authors BEFORE contacting forge\n\n");
}
}
}

View file

@ -20,6 +20,7 @@ public class FMLCorePlugin implements IFMLLoadingPlugin
public String[] getASMTransformerClass()
{
return new String[] {
"net.minecraftforge.fml.common.asm.transformers.BlamingTransformer",
"net.minecraftforge.fml.common.asm.transformers.MarkerTransformer",
"net.minecraftforge.fml.common.asm.transformers.SideTransformer",
"net.minecraftforge.fml.common.asm.transformers.EventSubscriptionTransformer",

View file

@ -0,0 +1,29 @@
package net.minecraftforge.fml.debug;
import java.util.Map;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
public class FaultyCoreMod implements IFMLLoadingPlugin {
public static boolean enabled = false;
public String[] getASMTransformerClass()
{
return new String[] { FaultyTransformer.class.getName() };
}
public String getModContainerClass() { return null; }
public String getSetupClass() { return null; }
public void injectData(Map<String, Object> data) {}
public String getAccessTransformerClass() { return null; }
public static class FaultyTransformer implements IClassTransformer {
public byte[] transform(String name, String transformedName, byte[] basicClass)
{
if(enabled && name.equals("net.minecraft.client.gui.GuiMainMenu")) throw new RuntimeException("Faulty transformer test exception");
return basicClass;
}
}
}

View file

@ -0,0 +1,26 @@
/*package net.minecraftforge.fml.debug;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
@Mod(modid = Java8Debug.MODID, version = Java8Debug.VERSION)
public class Java8Debug implements ITest
{
public static final String MODID = "Java8Debug";
public static final String VERSION = "1.0";
@EventHandler
public void init(FMLInitializationEvent event)
{
System.out.println("Java8Debug mod loaded");
}
}
interface ITest
{
default int test()
{
return 42;
}
}*/