Derive modded bukkit baseline

- Read the modded bukkit-supertype baseline from core/purity-allowlist.txt
- Match nested classes against their top-level source entry
- Exempt VolmLib classes, which Iris' core ratchet does not govern
- Delete the hand-frozen knownBukkitSupertypeClasses inventory
This commit is contained in:
Brian Neumann-Fopiano
2026-09-08 03:40:51 -04:00
parent 0bdacc6fed
commit 5c84c6b8d8
3 changed files with 75 additions and 47 deletions
@@ -43,6 +43,8 @@ public final class ModdedArtifactVerifier {
// 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/";
// VolmLib is a separate library with its own build; Iris' core purity ratchet does not govern it.
private static final String SHARED_LIBRARY_PREFIX = "art/arcane/volmlib/";
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";
@@ -65,12 +67,14 @@ public final class ModdedArtifactVerifier {
}
/**
* @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.
* @param bukkitCoupledCoreClasses top-level internal class names (no {@code .class} suffix, no
* nested-class part) whose source file is listed in
* core/purity-allowlist.txt. A class with an {@code org.bukkit}
* supertype is accepted only when it is one of those, sits under
* the Bukkit platform package, or comes from VolmLib. Anything
* else is new Bukkit coupling in core and fails the build.
*/
public static void verify(File artifact, List<String> requiredEntries, Set<String> bukkitSupertypeBaseline) {
public static void verify(File artifact, List<String> requiredEntries, Set<String> bukkitCoupledCoreClasses) {
if (!artifact.isFile()) {
throw new GradleException("Missing modded Iris artifact: " + artifact.getAbsolutePath());
}
@@ -129,7 +133,8 @@ public final class ModdedArtifactVerifier {
}
}
if (!name.startsWith(BUKKIT_PLATFORM_PREFIX)
&& !bukkitSupertypeBaseline.contains(name)
&& !name.startsWith(SHARED_LIBRARY_PREFIX)
&& !bukkitCoupledCoreClasses.contains(topLevelClass(name))
&& hasBukkitSupertype(classFile)) {
bukkitSupertypeClasses.add(name);
}
@@ -154,7 +159,8 @@ public final class ModdedArtifactVerifier {
}
if (!bukkitSupertypeClasses.isEmpty()) {
throw new GradleException(artifact.getName() + " ships classes with an org.bukkit supertype outside "
+ BUKKIT_PLATFORM_PREFIX + ": " + preview(bukkitSupertypeClasses));
+ BUKKIT_PLATFORM_PREFIX + " whose source is not declared Bukkit-coupled in"
+ " core/purity-allowlist.txt: " + preview(bukkitSupertypeClasses));
}
verifyNestedJarDeclarations(artifact, jar, shippedNestedJars);
@@ -251,6 +257,12 @@ public final class ModdedArtifactVerifier {
}
}
private static String topLevelClass(String entryName) {
String internalName = entryName.substring(0, entryName.length() - ".class".length());
int nested = internalName.indexOf('$');
return nested < 0 ? internalName : internalName.substring(0, nested);
}
private static boolean hasBukkitSupertype(byte[] classFile) {
ClassReader reader;
try {
@@ -253,14 +253,51 @@ public class ModdedArtifactVerifierTest {
}
@Test
public void acceptsBaselinedBukkitSupertype() throws Exception {
String entryName = "art/arcane/iris/engine/platform/BukkitChunkGenerator.class";
public void acceptsBukkitSupertypeDeclaredInThePurityAllowlist() throws Exception {
Map<String, byte[]> entries = validEntries();
entries.put(entryName, classExtending("art/arcane/iris/engine/platform/BukkitChunkGenerator",
"org/bukkit/generator/ChunkGenerator"));
entries.put("art/arcane/iris/engine/platform/BukkitChunkGenerator.class",
classExtending("art/arcane/iris/engine/platform/BukkitChunkGenerator",
"org/bukkit/generator/ChunkGenerator"));
File artifact = createArtifact(entries);
ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, Set.of(entryName));
ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES,
Set.of("art/arcane/iris/engine/platform/BukkitChunkGenerator"));
}
@Test
public void acceptsBukkitSupertypeOnNestedClassOfAllowlistedSource() throws Exception {
Map<String, byte[]> entries = validEntries();
entries.put("art/arcane/iris/engine/object/IrisEntity$BukkitOps$1.class",
classImplementing("art/arcane/iris/engine/object/IrisEntity$BukkitOps$1",
"org/bukkit/event/Listener"));
File artifact = createArtifact(entries);
ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES,
Set.of("art/arcane/iris/engine/object/IrisEntity"));
}
@Test
public void acceptsBukkitSupertypeFromSharedLibrary() throws Exception {
Map<String, byte[]> entries = validEntries();
entries.put("art/arcane/volmlib/util/bukkit/Events.class",
classImplementing("art/arcane/volmlib/util/bukkit/Events", "org/bukkit/event/Listener"));
File artifact = createArtifact(entries);
ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES);
}
@Test
public void rejectsBukkitSupertypeMissingFromThePurityAllowlist() throws Exception {
Map<String, byte[]> entries = validEntries();
entries.put("art/arcane/iris/core/link/data/OraxenDataProvider.class",
classImplementing("art/arcane/iris/core/link/data/OraxenDataProvider",
"org/bukkit/event/Listener"));
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES,
Set.of("art/arcane/iris/core/link/data/ItemAdderDataProvider")));
assertTrue(failure.getMessage().contains("core/purity-allowlist.txt"));
}
private Map<String, byte[]> validEntries() {