Handle erroring during early mod construction phases, and actually report that into the error UI

by doing armslength exception handling and propagation.

Signed-off-by: cpw <cpw+github@weeksfamily.ca>
This commit is contained in:
cpw 2020-11-07 22:30:19 -05:00
parent 160f3f88f9
commit f78b943bdf
No known key found for this signature in database
GPG key ID: 8EB3DF749553B1B7
2 changed files with 45 additions and 8 deletions

View file

@ -268,8 +268,10 @@ public class ModLoader
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity())); final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
LOGGER.debug(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader()); LOGGER.debug(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader());
final List<ModContainer> containers = modFile.getScanResult().getTargets().entrySet().stream(). final List<ModContainer> containers = modFile.getScanResult().getTargets()
map(e -> buildModContainerFromTOML(modFile, modClassLoader, modInfoMap, e)) .entrySet()
.stream()
.map(e -> buildModContainerFromTOML(modFile, modClassLoader, modInfoMap, e))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
if (containers.size() != modInfoMap.size()) { if (containers.size() != modInfoMap.size()) {
@ -279,7 +281,8 @@ public class ModLoader
modInfoMap.size(), modInfoMap.values().stream().map(IModInfo::getModId).sorted().collect(Collectors.toList())); modInfoMap.size(), modInfoMap.values().stream().map(IModInfo::getModId).sorted().collect(Collectors.toList()));
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath())); loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
} }
return containers; // remove errored mod containers
return containers.stream().filter(obj -> !(obj instanceof ErroredModContainer)).collect(Collectors.toList());
} }
private ModContainer buildModContainerFromTOML(final ModFile modFile, final TransformingClassLoader modClassLoader, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) { private ModContainer buildModContainerFromTOML(final ModFile modFile, final TransformingClassLoader modClassLoader, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
@ -291,9 +294,10 @@ public class ModLoader
orElseThrow(()->new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId)); orElseThrow(()->new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId));
return languageLoader.loadMod(info, modClassLoader, modFile.getScanResult()); return languageLoader.loadMod(info, modClassLoader, modFile.getScanResult());
} catch (ModLoadingException mle) { } catch (ModLoadingException mle) {
// exceptions are caught and added to the error list for later handling. Null is returned here. // exceptions are caught and added to the error list for later handling
loadingExceptions.add(mle); loadingExceptions.add(mle);
return null; // return an errored container instance here, because we tried and failed building a container.
return new ErroredModContainer(mle);
} }
} }
@ -336,4 +340,20 @@ public class ModLoader
public static boolean isDataGenRunning () { public static boolean isDataGenRunning () {
return runningDataGen; return runningDataGen;
} }
static class ErroredModContainer extends ModContainer {
public ErroredModContainer(final ModLoadingException mle) {
super(mle.getModInfo());
}
@Override
public boolean matches(final Object mod) {
return false;
}
@Override
public Object getMod() {
return null;
}
}
} }

View file

@ -19,6 +19,10 @@
package net.minecraftforge.fml.javafmlmod; package net.minecraftforge.fml.javafmlmod;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.ModLoadingException;
import net.minecraftforge.fml.ModLoadingStage;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.forgespi.language.ILifecycleEvent; import net.minecraftforge.forgespi.language.ILifecycleEvent;
import net.minecraftforge.forgespi.language.IModLanguageProvider; import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.forgespi.language.IModInfo; import net.minecraftforge.forgespi.language.IModInfo;
@ -39,7 +43,6 @@ import static net.minecraftforge.fml.Logging.*;
public class FMLJavaModLanguageProvider implements IModLanguageProvider public class FMLJavaModLanguageProvider implements IModLanguageProvider
{ {
private static final Logger LOGGER = LogManager.getLogger(); private static final Logger LOGGER = LogManager.getLogger();
private static class FMLModTarget implements IModLanguageProvider.IModLanguageLoader { private static class FMLModTarget implements IModLanguageProvider.IModLanguageLoader {
@ -72,10 +75,24 @@ public class FMLJavaModLanguageProvider implements IModLanguageProvider
final Constructor<?> constructor = fmlContainer.getConstructor(IModInfo.class, String.class, ClassLoader.class, ModFileScanData.class); final Constructor<?> constructor = fmlContainer.getConstructor(IModInfo.class, String.class, ClassLoader.class, ModFileScanData.class);
return (T)constructor.newInstance(info, className, modClassLoader, modFileScanResults); return (T)constructor.newInstance(info, className, modClassLoader, modFileScanResults);
} }
catch (NoSuchMethodException | ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) // ALL exception handling has to be done through the classloader, because we're loaded in the wrong context, so any classes we just blind load will be in the wrong
// class loading context. Funky but works.
catch (InvocationTargetException e) {
LOGGER.fatal(LOADING, "Failed to build mod", e);
final Class<RuntimeException> mle = (Class<RuntimeException>)LamdbaExceptionUtils.uncheck(()->Class.forName("net.minecraftforge.fml.ModLoadingException", true, Thread.currentThread().getContextClassLoader()));
if (mle.isInstance(e.getTargetException())) {
throw mle.cast(e.getTargetException());
} else {
final Class<ModLoadingStage> mls = (Class<ModLoadingStage>) LamdbaExceptionUtils.uncheck(()->Class.forName("net.minecraftforge.fml.ModLoadingStage", true, Thread.currentThread().getContextClassLoader()));
throw LamdbaExceptionUtils.uncheck(()->LamdbaExceptionUtils.uncheck(()->mle.getConstructor(IModInfo.class, mls, String.class, Throwable.class)).newInstance(info, Enum.valueOf(mls, "CONSTRUCT"), "fml.modloading.failedtoloadmodclass", e));
}
}
catch (NoSuchMethodException | ClassNotFoundException | InstantiationException | IllegalAccessException e)
{ {
LOGGER.fatal(LOADING,"Unable to load FMLModContainer, wut?", e); LOGGER.fatal(LOADING,"Unable to load FMLModContainer, wut?", e);
throw new RuntimeException(e); final Class<RuntimeException> mle = (Class<RuntimeException>)LamdbaExceptionUtils.uncheck(()->Class.forName("net.minecraftforge.fml.ModLoadingException", true, Thread.currentThread().getContextClassLoader()));
final Class<ModLoadingStage> mls = (Class<ModLoadingStage>) LamdbaExceptionUtils.uncheck(()->Class.forName("net.minecraftforge.fml.ModLoadingStage", true, Thread.currentThread().getContextClassLoader()));
throw LamdbaExceptionUtils.uncheck(()->LamdbaExceptionUtils.uncheck(()->mle.getConstructor(IModInfo.class, mls, String.class, Throwable.class)).newInstance(info, Enum.valueOf(mls, "CONSTRUCT"), "fml.modloading.failedtoloadmodclass", e));
} }
} }
} }