Skip to content

Commit

Permalink
Deal with cache misses due to timestamps from using ZipFileSystem ins…
Browse files Browse the repository at this point in the history
…tead of ZipOutputStream to modify jars
  • Loading branch information
lukebemish committed Jan 7, 2025
1 parent aef7fa2 commit 24591d6
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package dev.lukebemish.taskgraphrunner.instrumentation;

import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import java.lang.invoke.ConstantBootstraps;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

final class ASMUtils {
private ASMUtils() {}

static ConstantDynamic booleanConstant(boolean bool) {
return new ConstantDynamic(
// booleans are funky in ConstantDynamics. Here's an alternative...
bool ? "TRUE" : "FALSE",
Boolean.class.descriptorString(),
new Handle(
Opcodes.H_INVOKESTATIC,
Type.getInternalName(ConstantBootstraps.class),
"getStaticFinal",
MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class, Class.class).descriptorString(),
false
),
Type.getType(Boolean.class)
);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
package dev.lukebemish.taskgraphrunner.instrumentation;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.util.zip.ZipOutputStream;
import java.util.ArrayList;
import java.util.List;

public final class AgentMain {
private AgentMain() {}

public static void premain(String args, Instrumentation instrumentation) throws UnmodifiableClassException {
public static void premain(String args, Instrumentation instrumentation) {
instrumentation.addTransformer(new ZipOutputStreamRetransformer(), true);
instrumentation.retransformClasses(ZipOutputStream.class);
instrumentation.addTransformer(new ZipFileSystemRetransformer(), true);
try {
Class<?> zipOutputStream = Class.forName("java.util.zip.ZipOutputStream");
instrumentation.retransformClasses(zipOutputStream);
} catch (Throwable t) {
System.err.printf("Failed to retransform ZipOutputStream: %s%n", t);
}
try {
List<Class<?>> classes = new ArrayList<>();
Class<?> zipFileSystem = Class.forName("jdk.nio.zipfs.ZipFileSystem");

var nestFinder = new Object() {
void findNested(Class<?> clazz) {
classes.add(clazz);
for (Class<?> nested : clazz.getDeclaredClasses()) {
findNested(nested);
}
}
};

nestFinder.findNested(zipFileSystem);
instrumentation.retransformClasses(classes.toArray(Class<?>[]::new));
} catch (Throwable t) {
System.err.printf("Failed to retransform ZipOutputStream: %s%n", t);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package dev.lukebemish.taskgraphrunner.instrumentation;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class ZipFileSystemRetransformer implements ClassFileTransformer {
ZipFileSystemRetransformer() {}

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// Let's just target everything nested in ZipFileSystem
if (className.equals("jdk/nio/zipfs/ZipFileSystem") || className.startsWith("jdk/nio/zipfs/ZipFileSystem$")) {
var classReader = new ClassReader(classfileBuffer);
var writer = new ClassWriter(0);
var visitor = new ClassVisitor(Opcodes.ASM9, writer) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
// We're less selective than with ZOS here -- since the entries are not created in the file system, we can get away with just replacing the call to System.currentTimeMillis
var delegate = super.visitMethod(access, name, descriptor, signature, exceptions);
return new MethodVisitor(Opcodes.ASM9, delegate) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if (name.equals("currentTimeMillis") && opcode == Opcodes.INVOKESTATIC && owner.equals("java/lang/System") && descriptor.equals("()J")) {
super.visitLdcInsn(0L);
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
};
}
};
classReader.accept(visitor, 0);
return writer.toByteArray();
}
return ClassFileTransformer.super.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void visitCode() {
false
),
ZipOutputStreamRetransformer.class.getName(),
booleanConstant(false),
ASMUtils.booleanConstant(false),
systemClassloader
);

Expand Down Expand Up @@ -122,22 +122,6 @@ private static ConstantDynamic invoke(String descriptor, Object handle, Object..
);
}

private static ConstantDynamic booleanConstant(boolean bool) {
return new ConstantDynamic(
// booleans are funky in ConstantDynamics. Here's an alternative...
bool ? "TRUE" : "FALSE",
Boolean.class.descriptorString(),
new Handle(
Opcodes.H_INVOKESTATIC,
Type.getInternalName(ConstantBootstraps.class),
"getStaticFinal",
MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class, Class.class).descriptorString(),
false
),
Type.getType(Boolean.class)
);
}

public static ZipEntry standardize(ZipEntry zipEntry) {
zipEntry.setCreationTime(FileTime.fromMillis(0));
zipEntry.setLastAccessTime(FileTime.fromMillis(0));
Expand Down

0 comments on commit 24591d6

Please sign in to comment.