mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 04:37:47 +00:00
Way too much
This commit is contained in:
@@ -21,9 +21,7 @@ public final class BukkitArtifactVerifier {
|
||||
// class under one of these is a NoClassDefFoundError waiting for the right code path.
|
||||
private static final List<String> SHIPPED_PREFIXES = List.of(
|
||||
"art/arcane/iris/",
|
||||
"art/arcane/volmlib/",
|
||||
"com/google/gson/",
|
||||
"com/googlecode/concurrentlinkedhashmap/"
|
||||
"art/arcane/volmlib/"
|
||||
);
|
||||
// Relocation targets for the libraries slimjar downloads and relocates at runtime. Compiled
|
||||
// references to them are correct and the classes are correctly absent from the jar.
|
||||
@@ -38,9 +36,12 @@ public final class BukkitArtifactVerifier {
|
||||
"art/arcane/iris/util/aether/",
|
||||
"art/arcane/iris/util/guice/",
|
||||
"art/arcane/iris/util/dom4j/",
|
||||
"art/arcane/iris/util/jaxen/"
|
||||
"art/arcane/iris/util/jaxen/",
|
||||
"art/arcane/iris/util/gson/",
|
||||
"art/arcane/iris/util/lru/",
|
||||
"art/arcane/iris/util/caffeine/",
|
||||
"art/arcane/iris/util/paralithic/"
|
||||
);
|
||||
private static final String CAFFEINE_CACHE_PACKAGE = "art/arcane/iris/util/caffeine/cache/";
|
||||
private static final String MATTER_SLICE_PACKAGE = "art/arcane/volmlib/util/matter/slices/";
|
||||
private static final String LANGUAGE_DIRECTORY = "languages/";
|
||||
|
||||
@@ -48,10 +49,14 @@ public final class BukkitArtifactVerifier {
|
||||
}
|
||||
|
||||
public static void verify(File artifact, List<String> requiredEntries, int minimumLocales,
|
||||
int minimumCaffeineFactories, int minimumMatterSlices) {
|
||||
int minimumMatterSlices, long maximumArtifactBytes) {
|
||||
if (!artifact.isFile()) {
|
||||
throw new GradleException("Missing Bukkit Iris artifact: " + artifact.getAbsolutePath());
|
||||
}
|
||||
if (artifact.length() > maximumArtifactBytes) {
|
||||
throw new GradleException(artifact.getName() + " is " + artifact.length()
|
||||
+ " bytes; Bukkit artifacts must not exceed " + maximumArtifactBytes + " bytes");
|
||||
}
|
||||
|
||||
try (JarFile jar = new JarFile(artifact)) {
|
||||
for (String requiredEntry : requiredEntries) {
|
||||
@@ -65,7 +70,6 @@ public final class BukkitArtifactVerifier {
|
||||
|
||||
Set<String> shippedClasses = new LinkedHashSet<>();
|
||||
int locales = 0;
|
||||
int caffeineFactories = 0;
|
||||
int matterSlices = 0;
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
@@ -84,9 +88,6 @@ public final class BukkitArtifactVerifier {
|
||||
|
||||
String internalName = name.substring(0, name.length() - ".class".length());
|
||||
shippedClasses.add(internalName);
|
||||
if (isGeneratedCaffeineFactory(internalName)) {
|
||||
caffeineFactories++;
|
||||
}
|
||||
if (internalName.startsWith(MATTER_SLICE_PACKAGE)) {
|
||||
matterSlices++;
|
||||
}
|
||||
@@ -96,14 +97,6 @@ public final class BukkitArtifactVerifier {
|
||||
throw new GradleException(artifact.getName() + " ships " + locales + " locale files; expected at least "
|
||||
+ minimumLocales);
|
||||
}
|
||||
// Caffeine picks its cache and node implementation with MethodHandles.Lookup.findClass on a
|
||||
// name built from the builder's feature flags. Nothing references these statically, so only a
|
||||
// population check can tell that an exclude or minimize() ate them.
|
||||
if (caffeineFactories < minimumCaffeineFactories) {
|
||||
throw new GradleException(artifact.getName() + " ships " + caffeineFactories
|
||||
+ " generated Caffeine cache classes; expected at least " + minimumCaffeineFactories
|
||||
+ ". Caffeine resolves these by name and cannot survive static pruning");
|
||||
}
|
||||
// Matter.read() resolves slice types from the canonical name stored in the payload.
|
||||
if (matterSlices < minimumMatterSlices) {
|
||||
throw new GradleException(artifact.getName() + " ships " + matterSlices
|
||||
@@ -148,23 +141,6 @@ public final class BukkitArtifactVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isGeneratedCaffeineFactory(String internalName) {
|
||||
if (!internalName.startsWith(CAFFEINE_CACHE_PACKAGE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String simpleName = internalName.substring(CAFFEINE_CACHE_PACKAGE.length());
|
||||
if (simpleName.isEmpty() || simpleName.indexOf('/') >= 0) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < simpleName.length(); i++) {
|
||||
if (simpleName.charAt(i) < 'A' || simpleName.charAt(i) > 'Z') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static byte[] readEntryBytes(JarFile jar, JarEntry entry) throws IOException {
|
||||
try (InputStream input = jar.getInputStream(entry)) {
|
||||
return input.readAllBytes();
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import org.gradle.api.GradleException;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public final class JarCompactor {
|
||||
private static final int BUFFER_BYTES = 64 * 1024;
|
||||
|
||||
private JarCompactor() {
|
||||
}
|
||||
|
||||
public static void compact(File artifact) {
|
||||
if (artifact == null || !artifact.isFile()) {
|
||||
throw new GradleException("Cannot compact missing jar artifact: " + artifact);
|
||||
}
|
||||
|
||||
Path source = artifact.toPath();
|
||||
Path temporary = null;
|
||||
try {
|
||||
temporary = Files.createTempFile(source.getParent(), artifact.getName(), ".compact");
|
||||
rewrite(source, temporary);
|
||||
replace(temporary, source);
|
||||
} catch (IOException exception) {
|
||||
if (temporary != null) {
|
||||
try {
|
||||
Files.deleteIfExists(temporary);
|
||||
} catch (IOException cleanupFailure) {
|
||||
exception.addSuppressed(cleanupFailure);
|
||||
}
|
||||
}
|
||||
throw new GradleException("Unable to compact jar artifact " + artifact.getAbsolutePath(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void rewrite(Path source, Path destination) throws IOException {
|
||||
byte[] buffer = new byte[BUFFER_BYTES];
|
||||
try (InputStream rawInput = new BufferedInputStream(Files.newInputStream(source));
|
||||
ZipInputStream input = new ZipInputStream(rawInput);
|
||||
OutputStream rawOutput = new BufferedOutputStream(Files.newOutputStream(destination));
|
||||
ZipOutputStream output = new ZipOutputStream(rawOutput)) {
|
||||
output.setLevel(9);
|
||||
ZipEntry entry;
|
||||
while ((entry = input.getNextEntry()) != null) {
|
||||
if (entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
ZipEntry compacted = copyMetadata(entry);
|
||||
output.putNextEntry(compacted);
|
||||
int read;
|
||||
while ((read = input.read(buffer)) >= 0) {
|
||||
if (read > 0) {
|
||||
output.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
output.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ZipEntry copyMetadata(ZipEntry source) {
|
||||
ZipEntry target = new ZipEntry(source.getName());
|
||||
target.setMethod(ZipEntry.DEFLATED);
|
||||
if (source.getTime() >= 0L) {
|
||||
target.setTime(source.getTime());
|
||||
}
|
||||
if (source.getComment() != null) {
|
||||
target.setComment(source.getComment());
|
||||
}
|
||||
if (source.getExtra() != null) {
|
||||
target.setExtra(source.getExtra());
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private static void replace(Path temporary, Path source) throws IOException {
|
||||
try {
|
||||
Files.move(
|
||||
temporary,
|
||||
source,
|
||||
StandardCopyOption.ATOMIC_MOVE,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (AtomicMoveNotSupportedException exception) {
|
||||
Files.move(temporary, source, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ public class BukkitArtifactVerifierTest {
|
||||
NMS_BINDING + ".class"
|
||||
);
|
||||
private static final int LOCALES = 2;
|
||||
private static final int CAFFEINE_FACTORIES = 2;
|
||||
private static final int MATTER_SLICES = 1;
|
||||
|
||||
@Rule
|
||||
@@ -40,7 +39,7 @@ public class BukkitArtifactVerifierTest {
|
||||
public void acceptsCompleteArtifact() throws Exception {
|
||||
File artifact = createArtifact(validEntries());
|
||||
|
||||
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES, MATTER_SLICES);
|
||||
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -50,8 +49,7 @@ public class BukkitArtifactVerifierTest {
|
||||
File artifact = createArtifact(entries);
|
||||
|
||||
GradleException failure = assertThrows(GradleException.class,
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
|
||||
MATTER_SLICES));
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
|
||||
assertTrue(failure.getMessage().contains(PLUGIN_DESCRIPTOR));
|
||||
}
|
||||
|
||||
@@ -62,8 +60,7 @@ public class BukkitArtifactVerifierTest {
|
||||
File artifact = createArtifact(entries);
|
||||
|
||||
GradleException failure = assertThrows(GradleException.class,
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
|
||||
MATTER_SLICES));
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
|
||||
assertTrue(failure.getMessage().contains(SLIMJAR_DEPENDENCIES));
|
||||
}
|
||||
|
||||
@@ -74,8 +71,7 @@ public class BukkitArtifactVerifierTest {
|
||||
File artifact = createArtifact(entries);
|
||||
|
||||
GradleException failure = assertThrows(GradleException.class,
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
|
||||
MATTER_SLICES));
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
|
||||
assertTrue(failure.getMessage().contains(SLIMJAR_RESOLUTIONS));
|
||||
}
|
||||
|
||||
@@ -86,8 +82,7 @@ public class BukkitArtifactVerifierTest {
|
||||
File artifact = createArtifact(entries);
|
||||
|
||||
GradleException failure = assertThrows(GradleException.class,
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
|
||||
MATTER_SLICES));
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
|
||||
assertTrue(failure.getMessage().contains("art/arcane/volmlib/util/noise/CNG"));
|
||||
}
|
||||
|
||||
@@ -96,21 +91,31 @@ public class BukkitArtifactVerifierTest {
|
||||
Map<String, byte[]> entries = validEntries();
|
||||
entries.put("art/arcane/iris/Consumer.class",
|
||||
classReferencing("art/arcane/iris/Consumer", "art/arcane/iris/util/kyori/adventure/text/Component"));
|
||||
entries.put("art/arcane/iris/GsonConsumer.class",
|
||||
classReferencing("art/arcane/iris/GsonConsumer", "art/arcane/iris/util/gson/Gson"));
|
||||
entries.put("art/arcane/iris/LruConsumer.class",
|
||||
classReferencing("art/arcane/iris/LruConsumer", "art/arcane/iris/util/lru/ConcurrentLinkedHashMap"));
|
||||
entries.put("art/arcane/iris/CaffeineConsumer.class",
|
||||
classReferencing("art/arcane/iris/CaffeineConsumer", "art/arcane/iris/util/caffeine/cache/Caffeine"));
|
||||
entries.put("art/arcane/iris/ParalithicConsumer.class",
|
||||
classReferencing("art/arcane/iris/ParalithicConsumer", "art/arcane/iris/util/paralithic/functions/Function"));
|
||||
File artifact = createArtifact(entries);
|
||||
|
||||
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES, MATTER_SLICES);
|
||||
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsStrippedCaffeineFactories() throws Exception {
|
||||
Map<String, byte[]> entries = validEntries();
|
||||
entries.remove("art/arcane/iris/util/caffeine/cache/SSMS.class");
|
||||
File artifact = createArtifact(entries);
|
||||
public void rejectsArtifactAboveConfiguredSize() throws Exception {
|
||||
File artifact = createArtifact(validEntries());
|
||||
|
||||
GradleException failure = assertThrows(GradleException.class,
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
|
||||
MATTER_SLICES));
|
||||
assertTrue(failure.getMessage().contains("generated Caffeine cache classes"));
|
||||
() -> BukkitArtifactVerifier.verify(
|
||||
artifact,
|
||||
REQUIRED_ENTRIES,
|
||||
LOCALES,
|
||||
MATTER_SLICES,
|
||||
artifact.length() - 1L));
|
||||
assertTrue(failure.getMessage().contains("must not exceed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -120,8 +125,7 @@ public class BukkitArtifactVerifierTest {
|
||||
File artifact = createArtifact(entries);
|
||||
|
||||
GradleException failure = assertThrows(GradleException.class,
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
|
||||
MATTER_SLICES));
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
|
||||
assertTrue(failure.getMessage().contains("locale files"));
|
||||
}
|
||||
|
||||
@@ -132,8 +136,7 @@ public class BukkitArtifactVerifierTest {
|
||||
File artifact = createArtifact(entries);
|
||||
|
||||
GradleException failure = assertThrows(GradleException.class,
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES,
|
||||
MATTER_SLICES));
|
||||
() -> BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE));
|
||||
assertTrue(failure.getMessage().contains("Matter slice types"));
|
||||
}
|
||||
|
||||
@@ -144,7 +147,7 @@ public class BukkitArtifactVerifierTest {
|
||||
classAnnotatedWith("art/arcane/iris/Annotated", "com/google/errorprone/annotations/CanIgnoreReturnValue"));
|
||||
File artifact = createArtifact(entries);
|
||||
|
||||
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, CAFFEINE_FACTORIES, MATTER_SLICES);
|
||||
BukkitArtifactVerifier.verify(artifact, REQUIRED_ENTRIES, LOCALES, MATTER_SLICES, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
private Map<String, byte[]> validEntries() {
|
||||
@@ -159,10 +162,6 @@ public class BukkitArtifactVerifierTest {
|
||||
entries.put("art/arcane/volmlib/util/noise/CNG.class", emptyClass("art/arcane/volmlib/util/noise/CNG"));
|
||||
entries.put("art/arcane/volmlib/util/matter/slices/BlockMatter.class",
|
||||
emptyClass("art/arcane/volmlib/util/matter/slices/BlockMatter"));
|
||||
entries.put("art/arcane/iris/util/caffeine/cache/SSMS.class",
|
||||
emptyClass("art/arcane/iris/util/caffeine/cache/SSMS"));
|
||||
entries.put("art/arcane/iris/util/caffeine/cache/SSLMS.class",
|
||||
emptyClass("art/arcane/iris/util/caffeine/cache/SSLMS"));
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class JarCompactorTest {
|
||||
@Rule
|
||||
public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void preservesFilesAndOmitsDirectoryEntries() throws Exception {
|
||||
File artifact = temporaryFolder.newFile("artifact.jar");
|
||||
byte[] content = "Iris artifact content".getBytes(StandardCharsets.UTF_8);
|
||||
try (JarOutputStream output = new JarOutputStream(new FileOutputStream(artifact))) {
|
||||
output.putNextEntry(new JarEntry("example/"));
|
||||
output.closeEntry();
|
||||
output.putNextEntry(new JarEntry("example/value.txt"));
|
||||
output.write(content);
|
||||
output.closeEntry();
|
||||
}
|
||||
|
||||
JarCompactor.compact(artifact);
|
||||
|
||||
try (JarFile jar = new JarFile(artifact)) {
|
||||
assertNull(jar.getJarEntry("example/"));
|
||||
assertArrayEquals(content, jar.getInputStream(
|
||||
jar.getJarEntry("example/value.txt")).readAllBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user