Skip to content

Remove MagicAccessor, add MethodHandle - upgrade to JDK 24+#103

Draft
PRIESt512 wants to merge 1 commit intoodnoklassniki:masterfrom
PRIESt512:fix_magic_accessor
Draft

Remove MagicAccessor, add MethodHandle - upgrade to JDK 24+#103
PRIESt512 wants to merge 1 commit intoodnoklassniki:masterfrom
PRIESt512:fix_magic_accessor

Conversation

@PRIESt512
Copy link
Copy Markdown

Background

After the removal of MagicAccessorImpl, a replacement is needed to make serialization work in newer versions of Java.
A MethodHandle-based replacement was chosen as an alternative.

For BigDecimal the following classes are generated:

public final class Delegate0_BigDecimal implements Delegate {
    private final Map fields;
    private static final MethodHandle WRITE_OBJECT__VOID_MH;
    private static final MethodHandle READ_OBJECT__VOID_MH;
    private static final MethodHandle INTVAL_GET_BIGINTEGER_MH;
    private static final MethodHandle INTVAL_SET_BIGINTEGER_MH;
    private static final MethodHandle SCALE_GET_INT_MH;
    private static final MethodHandle SCALE_SET_INT_MH;

    public Delegate0_BigDecimal(Map var1) {
        this.fields = var1;
    }

    static {
        try {
            MethodHandles.Lookup var0 = MethodHandles.privateLookupIn(BigDecimal.class, MethodHandles.lookup());
            WRITE_OBJECT__VOID_MH = var0.findVirtual(BigDecimal.class, "writeObject", MethodHandlesReflection.findMethodTypeForWriteObject());
            READ_OBJECT__VOID_MH = var0.findVirtual(BigDecimal.class, "readObject", MethodHandlesReflection.findMethodTypeForReadObject());
            Field var2 = BigDecimal.class.getDeclaredField("intVal");
            var2.setAccessible(true);
            INTVAL_SET_BIGINTEGER_MH = var0.unreflectSetter(var2);
            INTVAL_GET_BIGINTEGER_MH = var0.unreflectGetter(var2);
            var2 = BigDecimal.class.getDeclaredField("scale");
            var2.setAccessible(true);
            SCALE_SET_INT_MH = var0.unreflectSetter(var2);
            SCALE_GET_INT_MH = var0.unreflectGetter(var2);
        } catch (ReflectiveOperationException var3) {
            throw new ExceptionInInitializerError(var3);
        } catch (Throwable var4) {
            throw new RuntimeException(var4);
        }
    }

    public final void calcSize(Object var1, CalcSizeStream var2) throws IOException {
        try {
            BigDecimal var5 = (BigDecimal)var1;
            WRITE_OBJECT__VOID_MH.invokeExact(var5, NullObjectOutputStream.INSTANCE);
            var2.writeObject(INTVAL_GET_BIGINTEGER_MH.invokeExact(var5));
            var2.count += 4;
        } catch (Throwable var4) {
            throw new RuntimeException(var4);
        }
    }

    public final void write(Object var1, DataStream var2) throws IOException {
        try {
            BigDecimal var5 = (BigDecimal)var1;
            WRITE_OBJECT__VOID_MH.invokeExact(var5, NullObjectOutputStream.INSTANCE);
            var2.writeObject(INTVAL_GET_BIGINTEGER_MH.invokeExact(var5));
            var2.writeInt(SCALE_GET_INT_MH.invokeExact(var5));
        } catch (Throwable var4) {
            throw new RuntimeException(var4);
        }
    }

    public final Object read(DataStream var1) throws IOException, ClassNotFoundException {
        try {
            BigDecimal var2 = (BigDecimal)JavaInternals.unsafe.allocateInstance(BigDecimal.class);
            var1.register(var2);
            INTVAL_SET_BIGINTEGER_MH.invokeExact(var2, (BigInteger)var1.readObject());
            SCALE_SET_INT_MH.invokeExact(var2, var1.readInt());
            READ_OBJECT__VOID_MH.invokeExact(var2, new GetFieldInputStream(var2, this.fields));
            return var2;
        } catch (Exception var4) {
            throw new RuntimeException(var4);
        } catch (Throwable var5) {
            throw new RuntimeException(var5);
        }
    }

    public final void skip(DataStream var1) throws IOException, ClassNotFoundException {
        var1.readObject();
        var1.skipBytes(4);
    }

    public final void toJson(Object var1, StringBuilder var2) throws IOException {
        try {
            BigDecimal var5 = (BigDecimal)var1;
            var2.append("{\"intVal\":");
            Json.appendObject(var2, INTVAL_GET_BIGINTEGER_MH.invokeExact(var5));
            var2.append(",\"scale\":");
            var2.append(SCALE_GET_INT_MH.invokeExact(var5)).append('}');
        } catch (Throwable var4) {
            throw new RuntimeException(var4);
        }
    }

    public final Object fromJson(JsonReader var1) throws IOException, ClassNotFoundException {
        try {
            var1.expect(123, "Expected object");
            BigDecimal var2 = (BigDecimal)JavaInternals.unsafe.allocateInstance(BigDecimal.class);
            if (var1.skipWhitespace() != 125) {
                while(true) {
                    label35: {
                        var1.skipWhitespace();
                        var1.expect(58, "Expected key-value pair");
                        var1.skipWhitespace();
                        switch (var3) {
                            case "intVal":
                                INTVAL_SET_BIGINTEGER_MH.invokeExact(var2, var1.next == 110 ? (BigInteger)var1.readNull() : (BigInteger)var1.readObject(BigInteger.class));
                                break label35;
                            case "scale":
                                SCALE_SET_INT_MH.invokeExact(var2, var1.readInt());
                                break label35;
                        }

                        var1.readObject();
                    }

                    if (var1.skipWhitespace() == 125) {
                        break;
                    }

                    var1.expect(44, "Unexpected end of object");
                    var1.skipWhitespace();
                }
            }

            var1.read();
            READ_OBJECT__VOID_MH.invokeExact(var2, new GetFieldInputStream(var2, this.fields));
            return var2;
        } catch (Exception var4) {
            throw new RuntimeException(var4);
        } catch (Throwable var5) {
            throw new RuntimeException(var5);
        }
    }
}
public final class Delegate1_BigInteger implements Delegate {
    private final Map fields;
    private static final MethodHandle SIGNUM_GET_INT_MH;
    private static final MethodHandle SIGNUM_SET_INT_MH;
    private static final MethodHandle MAG_GET_INT_ARRAY_MH;
    private static final MethodHandle MAG_SET_INT_ARRAY_MH;
    private static final MethodHandle BITCOUNTPLUSONE_GET_INT_MH;
    private static final MethodHandle BITCOUNTPLUSONE_SET_INT_MH;
    private static final MethodHandle BITLENGTHPLUSONE_GET_INT_MH;
    private static final MethodHandle BITLENGTHPLUSONE_SET_INT_MH;
    private static final MethodHandle LOWESTSETBITPLUSTWO_GET_INT_MH;
    private static final MethodHandle LOWESTSETBITPLUSTWO_SET_INT_MH;
    private static final MethodHandle FIRSTNONZEROINTNUMPLUSTWO_GET_INT_MH;
    private static final MethodHandle FIRSTNONZEROINTNUMPLUSTWO_SET_INT_MH;

    public Delegate1_BigInteger(Map var1) {
        this.fields = var1;
    }

    static {
        try {
            MethodHandles.Lookup var0 = MethodHandles.privateLookupIn(BigInteger.class, MethodHandles.lookup());
            Field var2 = BigInteger.class.getDeclaredField("signum");
            var2.setAccessible(true);
            SIGNUM_SET_INT_MH = var0.unreflectSetter(var2);
            SIGNUM_GET_INT_MH = var0.unreflectGetter(var2);
            var2 = BigInteger.class.getDeclaredField("mag");
            var2.setAccessible(true);
            MAG_SET_INT_ARRAY_MH = var0.unreflectSetter(var2);
            MAG_GET_INT_ARRAY_MH = var0.unreflectGetter(var2);
            var2 = BigInteger.class.getDeclaredField("bitCountPlusOne");
            var2.setAccessible(true);
            BITCOUNTPLUSONE_SET_INT_MH = var0.unreflectSetter(var2);
            BITCOUNTPLUSONE_GET_INT_MH = var0.unreflectGetter(var2);
            var2 = BigInteger.class.getDeclaredField("bitLengthPlusOne");
            var2.setAccessible(true);
            BITLENGTHPLUSONE_SET_INT_MH = var0.unreflectSetter(var2);
            BITLENGTHPLUSONE_GET_INT_MH = var0.unreflectGetter(var2);
            var2 = BigInteger.class.getDeclaredField("lowestSetBitPlusTwo");
            var2.setAccessible(true);
            LOWESTSETBITPLUSTWO_SET_INT_MH = var0.unreflectSetter(var2);
            LOWESTSETBITPLUSTWO_GET_INT_MH = var0.unreflectGetter(var2);
            var2 = BigInteger.class.getDeclaredField("firstNonzeroIntNumPlusTwo");
            var2.setAccessible(true);
            FIRSTNONZEROINTNUMPLUSTWO_SET_INT_MH = var0.unreflectSetter(var2);
            FIRSTNONZEROINTNUMPLUSTWO_GET_INT_MH = var0.unreflectGetter(var2);
        } catch (ReflectiveOperationException var3) {
            throw new ExceptionInInitializerError(var3);
        } catch (Throwable var4) {
            throw new RuntimeException(var4);
        }
    }

    public final void calcSize(Object var1, CalcSizeStream var2) throws IOException {
        try {
            BigInteger var5 = (BigInteger)var1;
            var2.writeObject(MAG_GET_INT_ARRAY_MH.invokeExact(var5));
            var2.count += 20;
        } catch (Throwable var4) {
            throw new RuntimeException(var4);
        }
    }

    public final void write(Object var1, DataStream var2) throws IOException {
        try {
            BigInteger var5 = (BigInteger)var1;
            var2.writeInt(SIGNUM_GET_INT_MH.invokeExact(var5));
            var2.writeObject(MAG_GET_INT_ARRAY_MH.invokeExact(var5));
            var2.writeInt(BITCOUNTPLUSONE_GET_INT_MH.invokeExact(var5));
            var2.writeInt(BITLENGTHPLUSONE_GET_INT_MH.invokeExact(var5));
            var2.writeInt(LOWESTSETBITPLUSTWO_GET_INT_MH.invokeExact(var5));
            var2.writeInt(FIRSTNONZEROINTNUMPLUSTWO_GET_INT_MH.invokeExact(var5));
        } catch (Throwable var4) {
            throw new RuntimeException(var4);
        }
    }

    public final Object read(DataStream var1) throws IOException, ClassNotFoundException {
        try {
            BigInteger var2 = (BigInteger)JavaInternals.unsafe.allocateInstance(BigInteger.class);
            var1.register(var2);
            SIGNUM_SET_INT_MH.invokeExact(var2, var1.readInt());
            MAG_SET_INT_ARRAY_MH.invokeExact(var2, (int[])var1.readObject());
            BITCOUNTPLUSONE_SET_INT_MH.invokeExact(var2, var1.readInt());
            BITLENGTHPLUSONE_SET_INT_MH.invokeExact(var2, var1.readInt());
            LOWESTSETBITPLUSTWO_SET_INT_MH.invokeExact(var2, var1.readInt());
            FIRSTNONZEROINTNUMPLUSTWO_SET_INT_MH.invokeExact(var2, var1.readInt());
            return var2;
        } catch (Exception var4) {
            throw new RuntimeException(var4);
        } catch (Throwable var5) {
            throw new RuntimeException(var5);
        }
    }

    public final void skip(DataStream var1) throws IOException, ClassNotFoundException {
        var1.skipBytes(4);
        var1.readObject();
        var1.skipBytes(16);
    }

    public final void toJson(Object var1, StringBuilder var2) throws IOException {
        try {
            BigInteger var5 = (BigInteger)var1;
            var2.append("{\"signum\":");
            var2.append(SIGNUM_GET_INT_MH.invokeExact(var5)).append(",\"mag\":");
            Json.appendObject(var2, MAG_GET_INT_ARRAY_MH.invokeExact(var5));
            var2.append(",\"bitCountPlusOne\":");
            var2.append(BITCOUNTPLUSONE_GET_INT_MH.invokeExact(var5)).append(",\"bitLengthPlusOne\":");
            var2.append(BITLENGTHPLUSONE_GET_INT_MH.invokeExact(var5)).append(",\"lowestSetBitPlusTwo\":");
            var2.append(LOWESTSETBITPLUSTWO_GET_INT_MH.invokeExact(var5)).append(",\"firstNonzeroIntNumPlusTwo\":");
            var2.append(FIRSTNONZEROINTNUMPLUSTWO_GET_INT_MH.invokeExact(var5)).append('}');
        } catch (Throwable var4) {
            throw new RuntimeException(var4);
        }
    }

    public final Object fromJson(JsonReader var1) throws IOException, ClassNotFoundException {
        try {
            var1.expect(123, "Expected object");
            BigInteger var2 = (BigInteger)JavaInternals.unsafe.allocateInstance(BigInteger.class);
            if (var1.skipWhitespace() != 125) {
                while(true) {
                    label47: {
                        var1.skipWhitespace();
                        var1.expect(58, "Expected key-value pair");
                        var1.skipWhitespace();
                        switch (var3) {
                            case "signum":
                                SIGNUM_SET_INT_MH.invokeExact(var2, var1.readInt());
                                break label47;
                            case "mag":
                                MAG_SET_INT_ARRAY_MH.invokeExact(var2, var1.next == 110 ? (int[])var1.readNull() : (int[])var1.readObject(int[].class));
                                break label47;
                            case "lowestSetBitPlusTwo":
                                LOWESTSETBITPLUSTWO_SET_INT_MH.invokeExact(var2, var1.readInt());
                                break label47;
                            case "firstNonzeroIntNumPlusTwo":
                                FIRSTNONZEROINTNUMPLUSTWO_SET_INT_MH.invokeExact(var2, var1.readInt());
                                break label47;
                            case "bitLengthPlusOne":
                                BITLENGTHPLUSONE_SET_INT_MH.invokeExact(var2, var1.readInt());
                                break label47;
                            case "bitCountPlusOne":
                                BITCOUNTPLUSONE_SET_INT_MH.invokeExact(var2, var1.readInt());
                                break label47;
                        }

                        var1.readObject();
                    }

                    if (var1.skipWhitespace() == 125) {
                        break;
                    }

                    var1.expect(44, "Unexpected end of object");
                    var1.skipWhitespace();
                }
            }

            var1.read();
            return var2;
        } catch (Exception var4) {
            throw new RuntimeException(var4);
        } catch (Throwable var5) {
            throw new RuntimeException(var5);
        }
    }
}

For all other classes, similar generation logic occurs.

This branch passes all tests except one.nio.serial.SerializationTest#testStringBuilder, one.nio.serial.SerializationTest#testInetAddress

@PRIESt512
Copy link
Copy Markdown
Author

@lehvolk does this development branch make sense? I was expecting feedback on it)

@lehvolk
Copy link
Copy Markdown
Collaborator

lehvolk commented Sep 1, 2025

@PRIESt512 to be honest I've treated this branch as draft and haven't review it. Sorry for inconvenience

@max-kammerer implemented a fix #104 which has been tested on internal projects and seems almost ready for publishing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants