This commit is contained in:
Brian Neumann-Fopiano committed 2026-08-01 22:39:09 -04:00
1 parent 324cf9e095
commit a8217b42e9
220 files changed
+10626 -1661

No files matched your search

@@ -1,4 +1,5 @@
import org.gradle.api.GradleException;
import org.objectweb.asm.ClassReader;
import java.io.File;
import java.io.IOException;
@@ -12,6 +13,9 @@ import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class ModdedArtifactVerifier {
private static final String CODEC_CLASS = "art/arcane/volmlib/util/mantle/io/Lz4IOWorkerCodecSupport.class";
@@ -33,11 +37,40 @@ public final class ModdedArtifactVerifier {
"art/arcane/iris/shadow/oshi",
"art/arcane/iris/shadow/jna"
);
// ASM must always be relocated: the loaders put their own copy on the module layer and a second
// org.objectweb.asm package on the same layer is a boot failure.
private static final String ASM_PREFIX = "org/objectweb/asm/";
// Only the Bukkit platform binding is allowed to sit on org.bukkit types, and the modded jars
// exclude it outright. Kept as an exemption so the same check can run over the Bukkit artifact.
private static final String BUKKIT_PLATFORM_PREFIX = "art/arcane/iris/platform/bukkit/";
private static final String BUKKIT_TYPE_PREFIX = "org/bukkit/";
private static final String FABRIC_METADATA = "fabric.mod.json";
private static final String NEOFORGE_METADATA = "META-INF/neoforge.mods.toml";
private static final String NESTED_JAR_DIRECTORY = "META-INF/jars/";
private static final String MIXIN_CONFIGS_ATTRIBUTE = "MixinConfigs";
// Forge 26.2 ships upstream Mixin 0.8.7, whose MixinEnvironment.CompatibilityLevel enum ends at
// JAVA_21. Anything higher is a hard MixinInitialisationError at Forge boot even though Fabric's
// fork accepts it, so the shared configs are capped here.
private static final int MAX_MIXIN_COMPATIBILITY_LEVEL = 21;
private static final Pattern TOML_CONFIG_ASSIGNMENT = Pattern.compile("(?m)^\\s*config\\s*=\\s*\"([^\"]+)\"");
private static final Pattern COMPATIBILITY_LEVEL = Pattern.compile("\"compatibilityLevel\"\\s*:\\s*\"([^\"]*)\"");
private static final Pattern COMPATIBILITY_LEVEL_VALUE = Pattern.compile("JAVA_(\\d+)");
private static final Pattern QUOTED_JSON_FILE = Pattern.compile("\"([^\"]+\\.(?:json|jar))\"");
private ModdedArtifactVerifier() {
}
public static void verify(File artifact, List<String> requiredEntries) {
verify(artifact, requiredEntries, Set.of());
}
/**
* @param bukkitSupertypeBaseline entry names (as they appear in the jar) that are known to
* extend or implement an {@code org.bukkit} type. New offenders
* outside this frozen set fail the build; entries that disappear
* from the artifact are not an error.
*/
public static void verify(File artifact, List<String> requiredEntries, Set<String> bukkitSupertypeBaseline) {
if (!artifact.isFile()) {
throw new GradleException("Missing modded Iris artifact: " + artifact.getAbsolutePath());
}
@@ -52,6 +85,9 @@ public final class ModdedArtifactVerifier {
Set<String> bundledRuntimeEntries = new LinkedHashSet<>();
Set<String> privateReferenceClasses = new LinkedHashSet<>();
Set<String> codecReferences = new LinkedHashSet<>();
Set<String> asmEntries = new LinkedHashSet<>();
Set<String> shippedNestedJars = new LinkedHashSet<>();
Set<String> bukkitSupertypeClasses = new LinkedHashSet<>();
boolean oshiReference = false;
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
@@ -64,6 +100,12 @@ public final class ModdedArtifactVerifier {
if (startsWithAny(name, BUNDLED_RUNTIME_PREFIXES)) {
bundledRuntimeEntries.add(name);
}
if (name.startsWith(ASM_PREFIX)) {
asmEntries.add(name);
}
if (name.startsWith(NESTED_JAR_DIRECTORY) && name.endsWith(".jar")) {
shippedNestedJars.add(name);
}
if (isNestedJar(name)) {
inspectNestedJar(jar, entry, bundledRuntimeEntries, privateReferenceClasses);
}
@@ -71,7 +113,8 @@ public final class ModdedArtifactVerifier {
continue;
}
String bytecode = readClass(jar, entry);
byte[] classFile = readEntryBytes(jar, entry);
String bytecode = new String(classFile, StandardCharsets.ISO_8859_1);
if (containsAny(bytecode, PRIVATE_REFERENCE_PREFIXES)) {
privateReferenceClasses.add(name);
}
@@ -85,6 +128,11 @@ public final class ModdedArtifactVerifier {
}
}
}
if (!name.startsWith(BUKKIT_PLATFORM_PREFIX)
&& !bukkitSupertypeBaseline.contains(name)
&& hasBukkitSupertype(classFile)) {
bukkitSupertypeClasses.add(name);
}
}
if (!bundledRuntimeEntries.isEmpty()) {
@@ -101,14 +149,181 @@ public final class ModdedArtifactVerifier {
if (!oshiReference) {
throw new GradleException(artifact.getName() + " does not link hardware diagnostics to Minecraft's OSHI runtime");
}
if (!asmEntries.isEmpty()) {
throw new GradleException(artifact.getName() + " ships unrelocated ASM: " + preview(asmEntries));
}
if (!bukkitSupertypeClasses.isEmpty()) {
throw new GradleException(artifact.getName() + " ships classes with an org.bukkit supertype outside "
+ BUKKIT_PLATFORM_PREFIX + ": " + preview(bukkitSupertypeClasses));
}
verifyNestedJarDeclarations(artifact, jar, shippedNestedJars);
verifyMixinCompatibilityLevels(artifact, jar);
} catch (IOException e) {
throw new GradleException("Unable to verify modded Iris artifact " + artifact.getAbsolutePath(), e);
}
}
private static String readClass(JarFile jar, JarEntry entry) throws IOException {
private static void verifyNestedJarDeclarations(File artifact, JarFile jar, Set<String> shippedNestedJars)
throws IOException {
String metadata = readEntryText(jar, FABRIC_METADATA);
if (metadata == null) {
if (!shippedNestedJars.isEmpty()) {
throw new GradleException(artifact.getName() + " ships nested jars but has no " + FABRIC_METADATA
+ " to declare them: " + preview(shippedNestedJars));
}
return;
}
Set<String> declared = collectQuotedFiles(extractJsonArray(metadata, "jars"), ".jar");
Set<String> undeclared = new LinkedHashSet<>(shippedNestedJars);
undeclared.removeAll(declared);
if (!undeclared.isEmpty()) {
throw new GradleException(artifact.getName() + " ships nested jars that " + FABRIC_METADATA
+ " does not declare: " + preview(undeclared));
}
Set<String> missing = new LinkedHashSet<>(declared);
missing.removeAll(shippedNestedJars);
if (!missing.isEmpty()) {
throw new GradleException(artifact.getName() + " declares nested jars in " + FABRIC_METADATA
+ " that it does not ship: " + preview(missing));
}
}
private static void verifyMixinCompatibilityLevels(File artifact, JarFile jar) throws IOException {
Set<String> configs = new LinkedHashSet<>();
Manifest manifest = jar.getManifest();
if (manifest != null) {
String declared = manifest.getMainAttributes().getValue(MIXIN_CONFIGS_ATTRIBUTE);
if (declared != null) {
for (String config : declared.split(",")) {
String trimmed = config.trim();
if (!trimmed.isEmpty()) {
configs.add(trimmed);
}
}
}
}
String neoforgeMetadata = readEntryText(jar, NEOFORGE_METADATA);
if (neoforgeMetadata != null) {
Matcher matcher = TOML_CONFIG_ASSIGNMENT.matcher(neoforgeMetadata);
while (matcher.find()) {
configs.add(matcher.group(1));
}
}
String fabricMetadata = readEntryText(jar, FABRIC_METADATA);
if (fabricMetadata != null) {
configs.addAll(collectQuotedFiles(extractJsonArray(fabricMetadata, "mixins"), ".json"));
}
if (configs.isEmpty()) {
throw new GradleException(artifact.getName() + " registers no mixin configs; expected the "
+ MIXIN_CONFIGS_ATTRIBUTE + " manifest attribute (Forge), [[mixins]] (NeoForge), or a mixins "
+ "array (Fabric)");
}
for (String config : configs) {
String body = readEntryText(jar, config);
if (body == null) {
throw new GradleException(artifact.getName() + " registers mixin config " + config
+ " which is not in the jar");
}
Matcher matcher = COMPATIBILITY_LEVEL.matcher(body);
if (!matcher.find()) {
continue;
}
String level = matcher.group(1);
Matcher value = COMPATIBILITY_LEVEL_VALUE.matcher(level);
if (!value.matches()) {
throw new GradleException(artifact.getName() + " mixin config " + config
+ " has unparseable compatibilityLevel " + level);
}
if (Integer.parseInt(value.group(1)) > MAX_MIXIN_COMPATIBILITY_LEVEL) {
throw new GradleException(artifact.getName() + " mixin config " + config
+ " requests compatibilityLevel " + level + "; upstream Mixin 0.8.7 (Forge) tops out at JAVA_"
+ MAX_MIXIN_COMPATIBILITY_LEVEL);
}
}
}
private static boolean hasBukkitSupertype(byte[] classFile) {
ClassReader reader;
try {
reader = new ClassReader(classFile);
} catch (RuntimeException e) {
// Class file version newer than the bundled ASM. Nothing to assert about it.
return false;
}
String superName = reader.getSuperName();
if (superName != null && superName.startsWith(BUKKIT_TYPE_PREFIX)) {
return true;
}
for (String candidate : reader.getInterfaces()) {
if (candidate.startsWith(BUKKIT_TYPE_PREFIX)) {
return true;
}
}
return false;
}
/**
* Returns the balanced {@code [...]} block that follows {@code "key"}, or an empty string.
*/
private static String extractJsonArray(String json, String key) {
int keyIndex = json.indexOf('"' + key + '"');
if (keyIndex < 0) {
return "";
}
int open = json.indexOf('[', keyIndex);
if (open < 0) {
return "";
}
int depth = 0;
for (int i = open; i < json.length(); i++) {
char c = json.charAt(i);
if (c == '[') {
depth++;
} else if (c == ']') {
depth--;
if (depth == 0) {
return json.substring(open, i + 1);
}
}
}
return "";
}
private static Set<String> collectQuotedFiles(String region, String suffix) {
Set<String> found = new LinkedHashSet<>();
Matcher matcher = QUOTED_JSON_FILE.matcher(region);
while (matcher.find()) {
String value = matcher.group(1);
if (value.endsWith(suffix)) {
found.add(value);
}
}
return found;
}
private static String readEntryText(JarFile jar, String name) throws IOException {
JarEntry entry = jar.getJarEntry(name);
if (entry == null) {
return null;
}
return new String(readEntryBytes(jar, entry), StandardCharsets.UTF_8);
}
private static byte[] readEntryBytes(JarFile jar, JarEntry entry) throws IOException {
try (InputStream input = jar.getInputStream(entry)) {
return new String(input.readAllBytes(), StandardCharsets.ISO_8859_1);
return input.readAllBytes();
}
}
@@ -163,7 +378,7 @@ public final class ModdedArtifactVerifier {
private static boolean isNestedJar(String name) {
return name.endsWith(".jar")
&& (name.startsWith("META-INF/jars/") || name.startsWith("META-INF/jarjar/"));
&& (name.startsWith(NESTED_JAR_DIRECTORY) || name.startsWith("META-INF/jarjar/"));
}
private static boolean isNamedRuntimeLibrary(String name) {