import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import sun.misc.Unsafe;

class Issue {

    public static void main(String... args) throws Exception {
        var module = ModuleLayer.boot().findModule("jdk.compiler").orElseThrow();
        var packageName = "com.sun.tools.javac.jvm";
        var klass = Class.forName(module, packageName + ".Code");

        attemptExploit(klass);
        addOpens(module, packageName);
        attemptExploit(klass);
    }

    private static void attemptExploit(Class<?> klass) throws Exception {
        try {
            var result = klass.getMethod("truncate", int.class).invoke(null, 42);
            System.out.println("Actual behavior: " + result);
        } catch (IllegalAccessException e) {
            System.out.println("Expected behavior: " + e.getMessage());
        }
    }

    private static void addOpens(Module module, String packageName) throws Exception {
        var implAddOpens = Module.class.getDeclaredMethod("implAddOpens", String.class, Module.class);

        var unsafe = getUnsafe();
        var overrideOffset = unsafe.objectFieldOffset(FakeAccessibleObject.class.getDeclaredField("override"));
        unsafe.putBooleanVolatile(implAddOpens, overrideOffset, true);

        implAddOpens.invoke(module, packageName, Issue.class.getModule());
    }

    private static Unsafe getUnsafe() throws Exception {
        var theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        return (Unsafe) theUnsafe.get(null);
    }

    private static class FakeAccessibleObject {
        boolean override;
        static final Object reflectionFactory = null;
        volatile Object accessCheckCache;
        private static volatile boolean printStackWhenAccessFails;
        private static volatile boolean printStackPropertiesSet;
    }

} 