Commit 3c957d788ff474d8a80aad15c5c096c01eb2c9e2

Authored by Mumfrey
1 parent bd15e763

check for mod transformer injection errors and treat as critical errors

java/client/com/mumfrey/liteloader/client/ClientEvents.java
... ... @@ -481,7 +481,7 @@ public class ClientEvents extends Events<Minecraft, IntegratedServer>
481 481 catch (Throwable th)
482 482 {
483 483 this.mods.onLateInitFailed(initMod, th);
484   - LiteLoaderLogger.warning(th, "Error initialising mod %s", initMod.getName());
  484 + LiteLoaderLogger.warning(th, "Error calling late init for mod %s", initMod.getName());
485 485 }
486 486 }
487 487 }
... ...
java/client/com/mumfrey/liteloader/client/LiteLoaderPanelManager.java
... ... @@ -191,6 +191,12 @@ public class LiteLoaderPanelManager implements PanelManager<GuiScreen>
191 191 {
192 192 return this.mods.getStartupErrorCount();
193 193 }
  194 +
  195 + @Override
  196 + public int getCriticalErrorCount()
  197 + {
  198 + return this.mods.getCriticalErrorCount();
  199 + }
194 200  
195 201 private boolean isPanelSupportedOnScreen(GuiScreen guiScreen)
196 202 {
... ...
java/client/com/mumfrey/liteloader/client/gui/GuiLiteLoaderPanel.java
... ... @@ -121,7 +121,7 @@ public class GuiLiteLoaderPanel extends GuiScreen
121 121  
122 122 private boolean mouseOverLogo = false;
123 123  
124   - private int startupErrorCount = 0;
  124 + private int startupErrorCount = 0, criticalErrorCount = 0;
125 125  
126 126 /**
127 127 * @param minecraft
... ... @@ -144,6 +144,7 @@ public class GuiLiteLoaderPanel extends GuiScreen
144 144 this.settingsPanel = new GuiPanelSettings(this, minecraft);
145 145  
146 146 this.startupErrorCount = mods.getStartupErrorCount();
  147 + this.criticalErrorCount = mods.getCriticalErrorCount();
147 148 }
148 149  
149 150 /**
... ... @@ -465,7 +466,7 @@ public class GuiLiteLoaderPanel extends GuiScreen
465 466  
466 467 private void drawErrorTooltip(int left, int top)
467 468 {
468   - GuiLiteLoaderPanel.drawTooltip(this.fontRendererObj, I18n.format("gui.error.tooltip", this.startupErrorCount), left, top, this.width, this.height, 0xFF5555, 0xB0330000);
  469 + GuiLiteLoaderPanel.drawTooltip(this.fontRendererObj, I18n.format("gui.error.tooltip", this.startupErrorCount, this.criticalErrorCount), left, top, this.width, this.height, 0xFF5555, 0xB0330000);
469 470 }
470 471  
471 472 /* (non-Javadoc)
... ...
java/common/com/mumfrey/liteloader/core/Events.java
... ... @@ -99,6 +99,9 @@ public abstract class Events<TClient, TServer extends MinecraftServer> implement
99 99 */
100 100 protected void onStartupComplete()
101 101 {
  102 + LoadingProgress.setMessage("Checking mods...");
  103 + this.mods.onStartupComplete();
  104 +
102 105 LoadingProgress.setMessage("Initialising CoreProviders...");
103 106 this.loader.onStartupComplete();
104 107  
... ...
java/common/com/mumfrey/liteloader/core/LiteLoaderMods.java
... ... @@ -7,6 +7,7 @@ import java.util.HashMap;
7 7 import java.util.LinkedList;
8 8 import java.util.List;
9 9 import java.util.Map;
  10 +import java.util.Set;
10 11  
11 12 import javax.activity.InvalidActivityException;
12 13  
... ... @@ -18,6 +19,9 @@ import com.mumfrey.liteloader.common.LoadingProgress;
18 19 import com.mumfrey.liteloader.interfaces.LoaderEnumerator;
19 20 import com.mumfrey.liteloader.interfaces.Loadable;
20 21 import com.mumfrey.liteloader.interfaces.LoadableMod;
  22 +import com.mumfrey.liteloader.interfaces.TweakContainer;
  23 +import com.mumfrey.liteloader.launch.ClassTransformerManager;
  24 +import com.mumfrey.liteloader.launch.LiteLoaderTweaker;
21 25 import com.mumfrey.liteloader.launch.LoaderEnvironment;
22 26 import com.mumfrey.liteloader.launch.LoaderProperties;
23 27 import com.mumfrey.liteloader.modconfig.ConfigManager;
... ... @@ -88,7 +92,7 @@ public class LiteLoaderMods
88 92 */
89 93 protected final LinkedList<NonMod> disabledMods = new LinkedList<NonMod>();
90 94  
91   - private int startupErrorCount;
  95 + private int startupErrorCount, criticalErrorCount;
92 96  
93 97 LiteLoaderMods(LiteLoader loader, LoaderEnvironment environment, LoaderProperties properties, ConfigManager configManager)
94 98 {
... ... @@ -162,6 +166,11 @@ public class LiteLoaderMods
162 166 return this.startupErrorCount;
163 167 }
164 168  
  169 + public int getCriticalErrorCount()
  170 + {
  171 + return this.criticalErrorCount;
  172 + }
  173 +
165 174 public ModInfo<?> getModInfo(LiteMod instance)
166 175 {
167 176 for (Mod mod : this.allMods)
... ... @@ -670,6 +679,47 @@ public class LiteLoaderMods
670 679 return String.format("version.%s", modName.toLowerCase().replaceAll("[^a-z0-9_\\-\\.]", ""));
671 680 }
672 681  
  682 + void onStartupComplete()
  683 + {
  684 + this.validateModTransformers();
  685 + }
  686 +
  687 + /**
  688 + * Check that all specified mod transformers were injected successfully, tag mods with failed transformers
  689 + * as critically errored
  690 + */
  691 + private void validateModTransformers()
  692 + {
  693 + ClassTransformerManager transformerManager = LiteLoaderTweaker.getTransformerManager();
  694 + Set<String> injectedTransformers = transformerManager.getInjectedTransformers();
  695 +
  696 + for (Mod mod : this.loadedMods)
  697 + {
  698 + if (mod.hasClassTransformers())
  699 + {
  700 + List<String> modTransformers = ((TweakContainer<?>)mod.getContainer()).getClassTransformerClassNames();
  701 + for (String modTransformer : modTransformers)
  702 + {
  703 + if (!injectedTransformers.contains(modTransformer))
  704 + {
  705 + List<Throwable> throwables = transformerManager.getTransformerStartupErrors(modTransformer);
  706 + if (throwables != null)
  707 + {
  708 + for (Throwable th : throwables)
  709 + {
  710 + this.registerModStartupError(mod, th, true);
  711 + }
  712 + }
  713 + else
  714 + {
  715 + this.registerModStartupError(mod, new RuntimeException("Missing class transformer " + modTransformer), true);
  716 + }
  717 + }
  718 + }
  719 + }
  720 + }
  721 + }
  722 +
673 723 /**
674 724 * @param instance
675 725 * @param th
... ... @@ -685,7 +735,33 @@ public class LiteLoaderMods
685 735  
686 736 private void registerModStartupError(ModInfo<?> mod, Throwable th)
687 737 {
  738 + // This is a critical error if a mod has already injected a transformer, since it may have injected
  739 + // callbacks which it is not in a position to handle!
  740 + boolean critical = this.hasModInjectedTransformers(mod);
  741 +
  742 + this.registerModStartupError(mod, th, critical);
  743 + }
  744 +
  745 + private boolean hasModInjectedTransformers(ModInfo<?> mod)
  746 + {
  747 + if (!mod.hasClassTransformers()) return false;
  748 +
  749 + Set<String> injectedTransformers = LiteLoaderTweaker.getTransformerManager().getInjectedTransformers();
  750 + List<String> modTransformers = ((TweakContainer<?>)mod.getContainer()).getClassTransformerClassNames();
  751 +
  752 + for (String modTransformer : modTransformers)
  753 + {
  754 + if (injectedTransformers.contains(modTransformer))
  755 + return true;
  756 + }
  757 +
  758 + return false;
  759 + }
  760 +
  761 + private void registerModStartupError(ModInfo<?> mod, Throwable th, boolean critical)
  762 + {
688 763 this.startupErrorCount++;
  764 + if (critical) this.criticalErrorCount++;
689 765 mod.registerStartupError(th);
690 766 }
691 767  
... ...
java/common/com/mumfrey/liteloader/interfaces/PanelManager.java
... ... @@ -54,4 +54,9 @@ public interface PanelManager&lt;TParentScreen&gt; extends TickObserver, PostRenderObs
54 54 * @return
55 55 */
56 56 public abstract int getStartupErrorCount();
  57 +
  58 + /**
  59 + * @return
  60 + */
  61 + public abstract int getCriticalErrorCount();
57 62 }
... ...
java/common/com/mumfrey/liteloader/launch/ClassTransformerManager.java
1 1 package com.mumfrey.liteloader.launch;
2 2  
3   -import java.util.Arrays;
4   -import java.util.Collection;
5   -import java.util.HashMap;
6   -import java.util.HashSet;
7   -import java.util.Iterator;
8   -import java.util.List;
9   -import java.util.Map;
10   -import java.util.Set;
11   -import java.util.TreeSet;
  3 +import java.lang.reflect.Field;
  4 +import java.util.*;
12 5 import java.util.Map.Entry;
13 6  
  7 +import org.apache.logging.log4j.Logger;
  8 +import org.apache.logging.log4j.core.LogEvent;
  9 +import org.apache.logging.log4j.core.appender.AbstractAppender;
  10 +
14 11 import net.minecraft.launchwrapper.IClassTransformer;
15 12 import net.minecraft.launchwrapper.LaunchClassLoader;
  13 +import net.minecraft.launchwrapper.LogWrapper;
16 14  
17 15 import com.mumfrey.liteloader.transformers.PacketTransformer;
18 16 import com.mumfrey.liteloader.util.SortableValue;
... ... @@ -33,7 +31,7 @@ public class ClassTransformerManager
33 31 /**
34 32 * Transformers to inject
35 33 */
36   - private Set<String> injectedTransformers = new HashSet<String>();
  34 + private Set<String> pendingTransformers = new HashSet<String>();
37 35  
38 36 /**
39 37 * Transformers to inject after preInit but before the game starts, necessary for anything that needs to be downstream of forge
... ... @@ -51,11 +49,60 @@ public class ClassTransformerManager
51 49 private final List<String> requiredTransformers;
52 50  
53 51 /**
  52 + * Transformers successfully injected by us
  53 + */
  54 + private final Set<String> injectedTransformers = new HashSet<String>();
  55 +
  56 + /**
  57 + * Catalogue of transformer startup failures
  58 + */
  59 + private final Map<String, List<Throwable>> transformerStartupErrors = new HashMap<String, List<Throwable>>();
  60 +
  61 + private Logger attachedLog;
  62 +
  63 + private String pendingTransformer;
  64 +
  65 + class ThrowableObserver extends AbstractAppender
  66 + {
  67 + public ThrowableObserver()
  68 + {
  69 + super("Throwable Observer", null, null);
  70 + this.start();
  71 + }
  72 +
  73 + @Override
  74 + public void append(LogEvent event)
  75 + {
  76 + ClassTransformerManager.this.observeThrowable(event.getThrown());
  77 + }
  78 + }
  79 +
  80 + /**
54 81 * @param requiredTransformers
55 82 */
56 83 public ClassTransformerManager(List<String> requiredTransformers)
57 84 {
58 85 this.requiredTransformers = requiredTransformers;
  86 +
  87 + this.appendObserver();
  88 + }
  89 +
  90 + private void appendObserver()
  91 + {
  92 + try
  93 + {
  94 + Field fLogger = LogWrapper.class.getDeclaredField("myLog");
  95 + fLogger.setAccessible(true);
  96 + this.attachedLog = (Logger)fLogger.get(LogWrapper.log);
  97 + if (this.attachedLog instanceof org.apache.logging.log4j.core.Logger)
  98 + {
  99 + ((org.apache.logging.log4j.core.Logger)this.attachedLog).addAppender(new ThrowableObserver());
  100 + }
  101 + }
  102 + catch (Exception ex)
  103 + {
  104 + LiteLoaderLogger.warning("Failed to append ThrowableObserver to LogWrapper, transformer startup exceptions may not be logged");
  105 + }
59 106 }
60 107  
61 108 /**
... ... @@ -66,7 +113,7 @@ public class ClassTransformerManager
66 113 {
67 114 if (!this.gameStarted)
68 115 {
69   - this.injectedTransformers.add(transformerClass);
  116 + this.pendingTransformers.add(transformerClass);
70 117 return true;
71 118 }
72 119  
... ... @@ -81,7 +128,7 @@ public class ClassTransformerManager
81 128 {
82 129 if (!this.gameStarted)
83 130 {
84   - this.injectedTransformers.addAll(transformerClasses);
  131 + this.pendingTransformers.addAll(transformerClasses);
85 132 return true;
86 133 }
87 134  
... ... @@ -96,7 +143,7 @@ public class ClassTransformerManager
96 143 {
97 144 if (!this.gameStarted)
98 145 {
99   - this.injectedTransformers.addAll(Arrays.asList(transformerClasses));
  146 + this.pendingTransformers.addAll(Arrays.asList(transformerClasses));
100 147 return true;
101 148 }
102 149  
... ... @@ -108,12 +155,12 @@ public class ClassTransformerManager
108 155 */
109 156 void injectUpstreamTransformers(LaunchClassLoader classLoader)
110 157 {
111   - this.sieveAndSortPacketTransformers(classLoader, this.injectedTransformers);
  158 + this.sieveAndSortPacketTransformers(classLoader, this.pendingTransformers);
112 159  
113 160 for (String requiredTransformerClassName : this.requiredTransformers)
114 161 {
115 162 LiteLoaderLogger.info("Injecting required class transformer '%s'", requiredTransformerClassName);
116   - classLoader.registerTransformer(requiredTransformerClassName);
  163 + this.injectTransformer(classLoader, requiredTransformerClassName);
117 164 }
118 165  
119 166 for (Entry<String, TreeSet<SortableValue<String>>> packetClassTransformers : this.packetTransformers.entrySet())
... ... @@ -121,14 +168,15 @@ public class ClassTransformerManager
121 168 for (SortableValue<String> transformerInfo : packetClassTransformers.getValue())
122 169 {
123 170 String packetClass = packetClassTransformers.getKey();
  171 + String transformerClassName = transformerInfo.getValue();
124 172 if (packetClass.lastIndexOf('.') != -1) packetClass = packetClass.substring(packetClass.lastIndexOf('.') + 1);
125   - LiteLoaderLogger.info("Injecting packet class transformer '%s' for packet class '%s' with priority %d", transformerInfo.getValue(), packetClass, transformerInfo.getPriority());
126   - classLoader.registerTransformer(transformerInfo.getValue());
  173 + LiteLoaderLogger.info("Injecting packet class transformer '%s' for packet class '%s' with priority %d", transformerClassName, packetClass, transformerInfo.getPriority());
  174 + this.injectTransformer(classLoader, transformerClassName);
127 175 }
128 176 }
129 177  
130 178 // inject any transformers received after this point directly into the downstreamTransformers set
131   - this.injectedTransformers = this.downstreamTransformers;
  179 + this.pendingTransformers = this.downstreamTransformers;
132 180 }
133 181  
134 182 /**
... ... @@ -142,7 +190,7 @@ public class ClassTransformerManager
142 190 for (String transformerClassName : this.downstreamTransformers)
143 191 {
144 192 LiteLoaderLogger.info("Injecting additional class transformer class '%s'", transformerClassName);
145   - classLoader.registerTransformer(transformerClassName);
  193 + this.injectTransformer(classLoader, transformerClassName);
146 194 }
147 195  
148 196 this.downstreamTransformers.clear();
... ... @@ -211,4 +259,58 @@ public class ClassTransformerManager
211 259  
212 260 LiteLoaderLogger.info("Added %d packet transformer classes to the transformer list", registeredTransformers);
213 261 }
  262 +
  263 + private synchronized void injectTransformer(LaunchClassLoader classLoader, String transformerClassName)
  264 + {
  265 + // Assign pendingTransformer so that logged errors during transformer init can be put in the map
  266 + this.pendingTransformer = transformerClassName;
  267 +
  268 + // Register the transformer
  269 + classLoader.registerTransformer(transformerClassName);
  270 +
  271 + // Unassign pending transformer now init is completed
  272 + this.pendingTransformer = null;
  273 +
  274 + // Check whether the transformer was successfully injected, look for it in the transformer list
  275 + if (this.findTransformer(classLoader, transformerClassName) != null)
  276 + {
  277 + this.injectedTransformers.add(transformerClassName);
  278 + }
  279 + }
  280 +
  281 + public void observeThrowable(Throwable th)
  282 + {
  283 + if (th != null && this.pendingTransformer != null)
  284 + {
  285 + List<Throwable> transformerErrors = this.transformerStartupErrors.get(this.pendingTransformer);
  286 + if (transformerErrors == null)
  287 + {
  288 + transformerErrors = new ArrayList<Throwable>();
  289 + this.transformerStartupErrors.put(this.pendingTransformer, transformerErrors);
  290 + }
  291 + transformerErrors.add(th);
  292 + }
  293 + }
  294 +
  295 + private IClassTransformer findTransformer(LaunchClassLoader classLoader, String transformerClassName)
  296 + {
  297 + for (IClassTransformer transformer : classLoader.getTransformers())
  298 + {
  299 + if (transformer.getClass().getName().equals(transformerClassName))
  300 + return transformer;
  301 + }
  302 +
  303 + return null;
  304 + }
  305 +
  306 + public Set<String> getInjectedTransformers()
  307 + {
  308 + return Collections.unmodifiableSet(this.injectedTransformers);
  309 + }
  310 +
  311 + public List<Throwable> getTransformerStartupErrors(String transformerClassName)
  312 + {
  313 + List<Throwable> errorList = this.transformerStartupErrors.get(transformerClassName);
  314 + return errorList != null ? Collections.unmodifiableList(errorList) : null;
  315 + }
214 316 }
... ...
resources/assets/liteloader/lang/en_US.lang
... ... @@ -70,4 +70,4 @@ gui.log.closedialog=Close
70 70  
71 71 gui.error.title=Startup errors for %s
72 72  
73   -gui.error.tooltip=%d mod startup error(s) detected
74 73 \ No newline at end of file
  74 +gui.error.tooltip=%d mod startup error(s) detected (%d critical)
75 75 \ No newline at end of file
... ...