Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add workaround for KeySpliterator shuffling #198

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 8 additions & 0 deletions nondex-core/src/main/java/java/util/HashMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -1517,9 +1517,13 @@ public final long estimateSize() {
static final class KeySpliterator<K,V>
extends HashMapSpliterator<K,V>
implements Spliterator<K> {

private final KeySpliteratorShuffler<K, V> shuffler;

KeySpliterator(HashMap<K,V> m, int origin, int fence, int est,
int expectedModCount) {
super(m, origin, fence, est, expectedModCount);
this.shuffler = new KeySpliteratorShuffler<>(this);
}

public KeySpliterator<K,V> trySplit() {
Expand All @@ -1530,6 +1534,10 @@ public KeySpliterator<K,V> trySplit() {
}

public void forEachRemaining(Consumer<? super K> action) {
shuffler.forEachRemaining(action);
}

public void original_forEachRemaining(Consumer<? super K> action) {
int i, hi, mc;
if (action == null)
throw new NullPointerException();
Expand Down
24 changes: 24 additions & 0 deletions nondex-core/src/main/java/java/util/KeySpliteratorShuffler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package java.util;
import java.util.function.Consumer;
import java.util.HashMap.KeySpliterator;

public class KeySpliteratorShuffler<K, V> {

private final Iterator<K> iter;
private final KeySpliterator<K, V> keySpliterator;

public KeySpliteratorShuffler(KeySpliterator<K, V> ks) {
this.keySpliterator = ks;

final List<K> keys = new ArrayList<>();
keySpliterator.original_forEachRemaining(key -> keys.add(key));

List<K> res = edu.illinois.nondex.shuffling.ControlNondeterminism.shuffle(keys);

iter = res.iterator();
}

public void forEachRemaining(Consumer<? super K> action) {
iter.forEachRemaining(action);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing end-of-line at the end?

Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public static ClassVisitor construct(ClassVisitor cv, String clzToInstrument)
} else if (Instrumenter.hasClassEntry(Instrumenter.hashMapEntryName)) {
return new HashMapShufflingAdder(cv, "Entry");
}
} else if (clzToInstrument.equals(Instrumenter.keySpliteratorName)) {
return new HashMapKeySplitAdder(cv);
} else if (clzToInstrument.equals(Instrumenter.weakHashMapName)) {
return new WeakHashMapShufflingAdder(cv);
} else if (clzToInstrument.equals(Instrumenter.identityHashMapName)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package edu.illinois.nondex.instr;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.RecordComponentVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
public class HashKeySpliteratorASMDump implements Opcodes {

public static byte[] dump () {

ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
MethodVisitor methodVisitor;

classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "java/util/HashMap$KeySpliterator$KeySpliteratorShuffler", "<K:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/lang/Object;", "java/lang/Object", null);

classWriter.visitSource("HashMap$KeySpliterator$KeySpliteratorShuffler.java", null);

classWriter.visitInnerClass("java/util/HashMap$KeySpliterator", "java/util/HashMap", "KeySpliterator", ACC_FINAL | ACC_STATIC);

classWriter.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC | ACC_FINAL | ACC_STATIC);

{
fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "iter", "Ljava/util/Iterator;", "Ljava/util/Iterator<TK;>;", null);
fieldVisitor.visitEnd();
}
{
fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "keySpliterator", "Ljava/util/HashMap$KeySpliterator;", "Ljava/util/HashMap$KeySpliterator<TK;TV;>;", null);
fieldVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/util/HashMap$KeySpliterator;)V", "(Ljava/util/HashMap$KeySpliterator<TK;TV;>;)V", null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(12, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(13, label1);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitFieldInsn(PUTFIELD, "java/util/HashMap$KeySpliterator$KeySpliteratorShuffler", "keySpliterator", "Ljava/util/HashMap$KeySpliterator;");
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(16, label2);
methodVisitor.visitTypeInsn(NEW, "java/util/ArrayList");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false);
methodVisitor.visitVarInsn(ASTORE, 2);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(17, label3);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "java/util/HashMap$KeySpliterator$KeySpliteratorShuffler", "keySpliterator", "Ljava/util/HashMap$KeySpliterator;");
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitInvokeDynamicInsn("accept", "(Ljava/util/List;)Ljava/util/function/Consumer;", new Handle(Opcodes.H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false), new Object[]{Type.getType("(Ljava/lang/Object;)V"), new Handle(Opcodes.H_INVOKESTATIC, "java/util/HashMap$KeySpliterator$KeySpliteratorShuffler", "lambda$new$0", "(Ljava/util/List;Ljava/lang/Object;)V", false), Type.getType("(Ljava/lang/Object;)V")});
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/util/HashMap$KeySpliterator", "original_forEachRemaining", "(Ljava/util/function/Consumer;)V", false);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(20, label4);
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitMethodInsn(INVOKESTATIC, "edu/illinois/nondex/shuffling/ControlNondeterminism", "shuffle", "(Ljava/util/List;)Ljava/util/List;", false);
methodVisitor.visitVarInsn(ASTORE, 3);
Label label5 = new Label();
methodVisitor.visitLabel(label5);
methodVisitor.visitLineNumber(23, label5);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 3);
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true);
methodVisitor.visitFieldInsn(PUTFIELD, "java/util/HashMap$KeySpliterator$KeySpliteratorShuffler", "iter", "Ljava/util/Iterator;");
Label label6 = new Label();
methodVisitor.visitLabel(label6);
methodVisitor.visitLineNumber(24, label6);
methodVisitor.visitInsn(RETURN);
Label label7 = new Label();
methodVisitor.visitLabel(label7);
methodVisitor.visitLocalVariable("this", "Ljava/util/HashMap$KeySpliterator$KeySpliteratorShuffler;", "Ljava/util/HashMap$KeySpliterator$KeySpliteratorShuffler<TK;TV;>;", label0, label7, 0);
methodVisitor.visitLocalVariable("ks", "Ljava/util/HashMap$KeySpliterator;", "Ljava/util/HashMap$KeySpliterator<TK;TV;>;", label0, label7, 1);
methodVisitor.visitLocalVariable("keys", "Ljava/util/List;", "Ljava/util/List<TK;>;", label3, label7, 2);
methodVisitor.visitLocalVariable("res", "Ljava/util/List;", "Ljava/util/List<TK;>;", label5, label7, 3);
methodVisitor.visitMaxs(2, 4);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "forEachRemaining", "(Ljava/util/function/Consumer;)V", "(Ljava/util/function/Consumer<-TK;>;)V", null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(31, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "java/util/HashMap$KeySpliterator$KeySpliteratorShuffler", "iter", "Ljava/util/Iterator;");
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "forEachRemaining", "(Ljava/util/function/Consumer;)V", true);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(32, label1);
methodVisitor.visitInsn(RETURN);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLocalVariable("this", "Ljava/util/HashMap$KeySpliterator$KeySpliteratorShuffler;", "Ljava/util/HashMap$KeySpliterator$KeySpliteratorShuffler<TK;TV;>;", label0, label2, 0);
methodVisitor.visitLocalVariable("action", "Ljava/util/function/Consumer;", "Ljava/util/function/Consumer<-TK;>;", label0, label2, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, "lambda$new$0", "(Ljava/util/List;Ljava/lang/Object;)V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(17, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
methodVisitor.visitInsn(POP);
methodVisitor.visitInsn(RETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("keys", "Ljava/util/List;", null, label0, label1, 0);
methodVisitor.visitLocalVariable("key", "Ljava/lang/Object;", null, label0, label1, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(7, label0);
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("YW=============shuffling ============");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(8, label1);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(2, 0);
methodVisitor.visitEnd();
}
classWriter.visitEnd();

return classWriter.toByteArray();
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line at the end?

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package edu.illinois.nondex.instr;

import org.objectweb.asm.*;

public class HashMapKeySplitAdder extends ClassVisitor {
public HashMapKeySplitAdder(ClassVisitor ca) {
super(Opcodes.ASM9, ca);
}

public void addSplitShufflerField() {
FieldVisitor fv = super.visitField(
Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL,
"shuffler",
"Ljava/util/HashMap$KeySpliterator$KeySpliteratorShuffler;",
"Ljava/util/HashMap<TK;TV;>.KeySpliterator.KeySpliteratorShuffler;",
null
);
fv.visitEnd();
}

public void addForEachRemaining() {
MethodVisitor methodVisitor = super.visitMethod(Opcodes.ACC_PUBLIC, "forEachRemaining", "(Ljava/util/function/Consumer;)V", "(Ljava/util/function/Consumer<-TK;>;)V", null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, "java/util/HashMap$KeySpliterator", "shuffler", "Ljava/util/HashMap$KeySpliterator$KeySpliteratorShuffler;");
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/HashMap$KeySpliterator$KeySpliteratorShuffler", "forEachRemaining", "(Ljava/util/function/Consumer;)V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitInsn(Opcodes.RETURN);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLocalVariable("this", "Ljava/util/HashMap$KeySpliterator;", "Ljava/util/HashMap$KeySpliterator<TK;TV;>;", label0, label2, 0);
methodVisitor.visitLocalVariable("action", "Ljava/util/function/Consumer;", "Ljava/util/function/Consumer<-TK;>;", label0, label2, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
}

@Override
public void visitEnd() {
addSplitShufflerField();
addForEachRemaining();
super.visitInnerClass("java/util/HashMap$KeySpliterator$KeySpliteratorShuffler", "java/util/HashMap$KeySpliterator",
"KeySpliteratorShuffler", 0);
super.visitEnd();
}

@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if ("<init>".equals(name)) {
return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, desc, signature, exceptions)) {
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.RETURN) {
super.visitVarInsn(Opcodes.ALOAD, 0); // Push "this" (KeySpliterator)
super.visitTypeInsn(Opcodes.NEW, "java/util/HashMap$KeySpliterator$KeySpliteratorShuffler"); // Create new KeySpliteratorShuffler
super.visitInsn(Opcodes.DUP); // Duplicate the reference to the new object
super.visitVarInsn(Opcodes.ALOAD, 0); // Push "this" again for the constructor
super.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/HashMap$KeySpliterator$KeySpliteratorShuffler",
"<init>", "(Ljava/util/HashMap$KeySpliterator;)V", false); // Call constructor
super.visitFieldInsn(Opcodes.PUTFIELD, "java/util/HashMap$KeySpliterator", "shuffler",
"Ljava/util/HashMap$KeySpliterator$KeySpliteratorShuffler;"); // Assign to shuffler
}
super.visitInsn(opcode);
}
};
}
if ("forEachRemaining".equals(name)) {
// Rename the existing method to original_forEachRemaining
return super.visitMethod(access, "original_forEachRemaining", desc, signature, exceptions);
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ a copy of this software and associated documentation files (the

public final class Instrumenter {
public static final String hashMapName = "java/util/HashMap$HashIterator.class";
public static final String keySpliteratorName = "java/util/HashMap$KeySpliterator.class";
public static final String weakHashMapName = "java/util/WeakHashMap$HashIterator.class";
public static final String identityHashMapName = "java/util/IdentityHashMap$IdentityHashMapIterator.class";
public static final String concurrentHashMapName = "java/util/concurrent/ConcurrentHashMap$Traverser.class";
Expand All @@ -72,6 +73,7 @@ public final class Instrumenter {
public static final String hashMapNodeName = "java/util/HashMap$Node.class";
public static final String hashMapEntryName = "java/util/HashMap$Entry.class";
public static final String hashMapHashIteratorShufflerName = "java/util/HashMap$HashIterator$HashIteratorShuffler.class";
public static final String hashMapKeySpliteratorShufflerName = "java/util/HashMap$KeySpliterator$KeySpliteratorShuffler.class";

private static final String rootPath = "modules/java.base";

Expand All @@ -97,6 +99,7 @@ private Instrumenter() {
this.standardClassesToInstrument.add("java/util/PriorityQueue.class");

this.specialClassesToInstrument.add(Instrumenter.hashMapName);
this.specialClassesToInstrument.add(Instrumenter.keySpliteratorName);
this.specialClassesToInstrument.add(Instrumenter.weakHashMapName);
this.specialClassesToInstrument.add(Instrumenter.identityHashMapName);
this.specialClassesToInstrument.add(Instrumenter.concurrentHashMapName);
Expand Down Expand Up @@ -203,6 +206,14 @@ public byte[] apply() {
}
});
}

// add SpliteratorShuffler.class
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be done only in some cases, like the if-then-else above?

Copy link
Author

@everbrightw everbrightw Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm for this key case, we do not have to do this.

We probably will need this for the EntrySpliterator, but I can look into this to confirm further (for EntrySpliterator).

this.addAsmDumpResultToZip(outZip, hashMapKeySpliteratorShufflerName, new Producer<byte[]>() {
@Override
public byte[] apply() {
return HashKeySpliteratorASMDump.dump();
}
});

for (String clz : this.specialClassesToInstrument) {
this.instrumentSpecialClass(outZip, clz);
Expand Down