Cleanup and ensure extensions have valid names.

This commit is contained in:
Articdive 2020-10-24 10:45:27 +02:00
parent 3e59c9d396
commit 6bb7186b3f
No known key found for this signature in database
GPG Key ID: B069585F0F7D90DE
2 changed files with 61 additions and 27 deletions

View File

@ -1,6 +1,10 @@
package net.minestom.server.extensions; package net.minestom.server.extensions;
import com.google.gson.*; import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader;
import net.minestom.server.utils.validate.Check; import net.minestom.server.utils.validate.Check;
@ -9,14 +13,23 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Mixins; import org.spongepowered.asm.mixin.Mixins;
import java.io.*; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@Slf4j @Slf4j
@ -58,8 +71,9 @@ public class ExtensionManager {
loader = newClassLoader(urls); loader = newClassLoader(urls);
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
log.error("Failed to get URL.", e); log.error("Failed to get URL.", e);
return; continue;
} }
// TODO: Can't we use discoveredExtension.description here? Someone should test that.
final InputStream extensionInputStream = loader.getResourceAsStream("extension.json"); final InputStream extensionInputStream = loader.getResourceAsStream("extension.json");
if (extensionInputStream == null) { if (extensionInputStream == null) {
StringBuilder urlsString = new StringBuilder(); StringBuilder urlsString = new StringBuilder();
@ -71,12 +85,18 @@ public class ExtensionManager {
urlsString.append("'").append(url.toString()).append("'"); urlsString.append("'").append(url.toString()).append("'");
} }
log.error("Failed to find extension.json in the urls '{}'.", urlsString); log.error("Failed to find extension.json in the urls '{}'.", urlsString);
return; continue;
} }
JsonObject extensionDescriptionJson = JsonParser.parseReader(new InputStreamReader(extensionInputStream)).getAsJsonObject(); JsonObject extensionDescriptionJson = JsonParser.parseReader(new InputStreamReader(extensionInputStream)).getAsJsonObject();
final String mainClass = extensionDescriptionJson.get("entrypoint").getAsString(); final String mainClass = extensionDescriptionJson.get("entrypoint").getAsString();
final String extensionName = extensionDescriptionJson.get("name").getAsString(); final String extensionName = extensionDescriptionJson.get("name").getAsString();
// Check the validity of the extension's name.
if (!extensionName.matches("[A-Za-z]+")) {
log.error("Extension '{}' specified an invalid name.", extensionName);
log.error("Extension '{}' will not be loaded.", extensionName);
continue;
}
// Get ExtensionDescription (authors, version etc.) // Get ExtensionDescription (authors, version etc.)
Extension.ExtensionDescription extensionDescription; Extension.ExtensionDescription extensionDescription;
@ -86,22 +106,24 @@ public class ExtensionManager {
log.warn("Extension '{}' did not specify a version.", extensionName); log.warn("Extension '{}' did not specify a version.", extensionName);
log.warn("Extension '{}' will continue to load but should specify a plugin version.", extensionName); log.warn("Extension '{}' will continue to load but should specify a plugin version.", extensionName);
version = "Not Specified"; version = "Not Specified";
} else } else {
version = extensionDescriptionJson.get("version").getAsString(); version = extensionDescriptionJson.get("version").getAsString();
}
List<String> authors; List<String> authors;
if (!extensionDescriptionJson.has("authors")) { if (!extensionDescriptionJson.has("authors")) {
authors = new ArrayList<>(); authors = new ArrayList<>();
} else } else {
authors = Arrays.asList(new Gson().fromJson(extensionDescriptionJson.get("authors"), String[].class)); authors = Arrays.asList(new Gson().fromJson(extensionDescriptionJson.get("authors"), String[].class));
}
extensionDescription = new Extension.ExtensionDescription(extensionName, version, authors); extensionDescription = new Extension.ExtensionDescription(extensionName, version, authors);
} }
extensionLoaders.put(extensionName, loader); extensionLoaders.put(extensionName.toLowerCase(), loader);
if (extensions.containsKey(extensionName.toLowerCase())) { if (extensions.containsKey(extensionName.toLowerCase())) {
log.error("An extension called '{}' has already been registered.", extensionName); log.error("An extension called '{}' has already been registered.", extensionName);
return; continue;
} }
Class<?> jarClass; Class<?> jarClass;
@ -109,15 +131,15 @@ public class ExtensionManager {
jarClass = Class.forName(mainClass, true, loader); jarClass = Class.forName(mainClass, true, loader);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
log.error("Could not find main class '{}' in extension '{}'.", mainClass, extensionName, e); log.error("Could not find main class '{}' in extension '{}'.", mainClass, extensionName, e);
return; continue;
} }
Class<? extends Extension> extensionClass; Class<? extends Extension> extensionClass;
try { try {
extensionClass = jarClass.asSubclass(Extension.class); extensionClass = jarClass.asSubclass(Extension.class);
} catch (ClassCastException e) { } catch (ClassCastException e) {
log.error("Main class '{}' in '{}' does not extend the 'extension superclass'.", mainClass, extensionName, e); log.error("Main class '{}' in '{}' does not extend the 'Extension' superclass.", mainClass, extensionName, e);
return; continue;
} }
Constructor<? extends Extension> constructor; Constructor<? extends Extension> constructor;
@ -127,15 +149,14 @@ public class ExtensionManager {
constructor.setAccessible(true); constructor.setAccessible(true);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
log.error("Main class '{}' in '{}' does not define a no-args constructor.", mainClass, extensionName, e); log.error("Main class '{}' in '{}' does not define a no-args constructor.", mainClass, extensionName, e);
return; continue;
} }
Extension extension = null; Extension extension = null;
try { try {
// Is annotated with NotNull
extension = constructor.newInstance(); extension = constructor.newInstance();
} catch (InstantiationException e) { } catch (InstantiationException e) {
log.error("Main class '{}' in '{}' cannot be an abstract class.", mainClass, extensionName, e); log.error("Main class '{}' in '{}' cannot be an abstract class.", mainClass, extensionName, e);
return; continue;
} catch (IllegalAccessException ignored) { } catch (IllegalAccessException ignored) {
// We made it accessible, should not occur // We made it accessible, should not occur
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
@ -145,9 +166,10 @@ public class ExtensionManager {
extensionName, extensionName,
e.getTargetException() e.getTargetException()
); );
return; continue;
} }
// Set description
// Set extension description
try { try {
Field descriptionField = extensionClass.getSuperclass().getDeclaredField("description"); Field descriptionField = extensionClass.getSuperclass().getDeclaredField("description");
descriptionField.setAccessible(true); descriptionField.setAccessible(true);
@ -156,9 +178,10 @@ public class ExtensionManager {
// We made it accessible, should not occur // We made it accessible, should not occur
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
log.error("Main class '{}' in '{}' has no description field.", mainClass, extensionName, e); log.error("Main class '{}' in '{}' has no description field.", mainClass, extensionName, e);
return; continue;
} }
// Set Logger
// Set logger
try { try {
Field descriptionField = extensionClass.getSuperclass().getDeclaredField("logger"); Field descriptionField = extensionClass.getSuperclass().getDeclaredField("logger");
descriptionField.setAccessible(true); descriptionField.setAccessible(true);
@ -167,6 +190,7 @@ public class ExtensionManager {
// We made it accessible, should not occur // We made it accessible, should not occur
e.printStackTrace(); e.printStackTrace();
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
// This should also not occur (unless someone changed the logger in Extension superclass).
log.error("Main class '{}' in '{}' has no logger field.", mainClass, extensionName, e); log.error("Main class '{}' in '{}' has no logger field.", mainClass, extensionName, e);
} }
@ -174,6 +198,7 @@ public class ExtensionManager {
} }
} }
@NotNull
private List<DiscoveredExtension> discoverExtensions() { private List<DiscoveredExtension> discoverExtensions() {
List<DiscoveredExtension> extensions = new LinkedList<>(); List<DiscoveredExtension> extensions = new LinkedList<>();
for (File file : extensionFolder.listFiles()) { for (File file : extensionFolder.listFiles()) {
@ -245,7 +270,7 @@ public class ExtensionManager {
/** /**
* Extensions are allowed to apply Mixin transformers, the magic happens here. * Extensions are allowed to apply Mixin transformers, the magic happens here.
*/ */
private void setupCodeModifiers(List<DiscoveredExtension> extensions) { private void setupCodeModifiers(@NotNull List<DiscoveredExtension> extensions) {
final ClassLoader cl = getClass().getClassLoader(); final ClassLoader cl = getClass().getClassLoader();
if (!(cl instanceof MinestomOverwriteClassLoader)) { if (!(cl instanceof MinestomOverwriteClassLoader)) {
log.warn("Current class loader is not a MinestomOverwriteClassLoader, but " + cl + ". This disables code modifiers (Mixin support is therefore disabled)"); log.warn("Current class loader is not a MinestomOverwriteClassLoader, but " + cl + ". This disables code modifiers (Mixin support is therefore disabled)");

View File

@ -1,22 +1,31 @@
package net.minestom.server.utils.validate; package net.minestom.server.utils.validate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
public class Check { public final class Check {
private Check() {
public static void notNull(Object object, String reason) { }
if (Objects.isNull(object))
public static void notNull(@Nullable Object object, @NotNull String reason) {
if (Objects.isNull(object)) {
throw new NullPointerException(reason); throw new NullPointerException(reason);
}
} }
public static void argCondition(boolean condition, String reason) { public static void argCondition(boolean condition, @NotNull String reason) {
if (condition) if (condition) {
throw new IllegalArgumentException(reason); throw new IllegalArgumentException(reason);
}
} }
public static void stateCondition(boolean condition, String reason) { public static void stateCondition(boolean condition, @NotNull String reason) {
if (condition) if (condition) {
throw new IllegalStateException(reason); throw new IllegalStateException(reason);
}
} }
} }