Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added lib/asm-util-9.6.jar
Binary file not shown.
4 changes: 0 additions & 4 deletions src/main/com/tonicsystems/jarjar/util/EntryStruct.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ public boolean isClass() {
if (!name.endsWith(".class")) {
return false;
}
if (name.startsWith("META-INF/version")) {
// TODO(b/69678527): handle multi-release jar files
return false;
}
return true;
}

Expand Down
10 changes: 7 additions & 3 deletions src/main/com/tonicsystems/jarjar/util/JarTransformer.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,19 @@ public boolean process(EntryStruct struct) throws IOException {
}
if (updateData) {
struct.data = w.toByteArray();
struct.name = pathFromName(w.getClassName());
struct.name = replaceName(struct.name, w.getClassName());
}
}
return true;
}

protected abstract ClassVisitor transform(ClassVisitor v);

private static String pathFromName(String className) {
return className.replace('.', '/') + ".class";
private static String replaceName(String name, String className) {
String prefix =
name.startsWith("META-INF/versions/")
? name.substring(0, name.indexOf('/', "META-INF/versions/".length()) + 1)
: "";
return prefix + className.replace('.', '/') + ".class";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Enumeration;
Expand All @@ -27,6 +29,14 @@
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import junit.framework.TestCase;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceClassVisitor;

@SuppressWarnings("JdkImmutableCollections")
public class StandaloneJarProcessorTest extends TestCase {
Expand Down Expand Up @@ -128,6 +138,31 @@ public void testOutput_emptyDirsAreDeleted() throws Exception {
createEntry("zaf/bar/A.class", "Hello")));
}

public void testProcessor_multiReleaseEntriesArePreserved() throws Exception {
assertJarTransformation(
List.of(
createEntry("foo/bar/A.class", createClass("foo/bar/A")),
createEntry("META-INF/versions/11/foo/bar/A.class", createClass("foo/bar/A")),
createEntry("META-INF/versions/21/foo/bar/A.class", createClass("foo/bar/A"))),
new JarTransformerChain(
new RemappingClassTransformer[] {
new RemappingClassTransformer(
new Remapper() {
@Override
public String map(final String internalName) {
if (internalName.equals("foo/bar/A")) {
return "foo/baz/B";
}
return internalName;
}
})
}),
List.of(
createEntry("META-INF/versions/11/foo/baz/B.class", createClass("foo/baz/B")),
createEntry("META-INF/versions/21/foo/baz/B.class", createClass("foo/baz/B")),
createEntry("foo/baz/B.class", createClass("foo/baz/B"))));
}

private void assertJarTransformation(
List<EntryStruct> inEntries, JarProcessor processor, List<EntryStruct> expectedEntries)
throws Exception {
Expand All @@ -141,15 +176,33 @@ private void assertJarTransformation(
EntryStruct actualEntry = actualEntries.get(i);
assertEquals(expectedEntry.name, actualEntry.name);
assertEquals(expectedEntry.time, actualEntry.time);
assertEquals(new String(expectedEntry.data, UTF_8), new String(actualEntry.data, UTF_8));
assertEquals(printData(expectedEntry.data), printData(actualEntry.data));
}
}

private String printData(byte[] data) {
Printer textifier = new Textifier();
StringWriter sw = new StringWriter();
try {
new ClassReader(data)
.accept(
new TraceClassVisitor(null, textifier, new PrintWriter(sw, true)),
ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
} catch (IndexOutOfBoundsException e) {
return new String(data, UTF_8);
}
return sw.toString();
}

private EntryStruct createEntry(String name, String data) {
return createEntry(name, data.getBytes(UTF_8));
}

private EntryStruct createEntry(String name, byte[] data) {
EntryStruct entry = new EntryStruct();
entry.name = name;
entry.time = ARBITRARY_INSTANT.toEpochMilli();
entry.data = data.getBytes(UTF_8);
entry.data = data;
return entry;
}

Expand Down Expand Up @@ -181,6 +234,21 @@ private List<EntryStruct> readJar(File jar) throws Exception {
return result;
}

private byte[] createClass(String name) {
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_SUPER, name, null, "java/lang/Object", null);

MethodVisitor methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(
Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitEnd();

return classWriter.toByteArray();
}

private static final Instant ARBITRARY_INSTANT = Instant.parse("2024-02-27T10:15:30.00Z");

public StandaloneJarProcessorTest(String name) {
Expand Down