alibaba / fastjson2

🚄 FASTJSON2 is a Java JSON library with excellent performance.
Apache License 2.0
3.79k stars 495 forks source link

[BUG] Failed to deserialize boolean field with false value in GraalVM native image. #3108

Open suragreat opened 1 month ago

suragreat commented 1 month ago

问题描述

If a bean contains a boolean field, it can't be deserialized with false value in GraalVM native image.

环境信息

重现步骤

I'd created a demo repo in: https://github.com/suragreat/fastjson2-native.

To run:

mvn clean package -Pnative

./target/my-app

public class App {
    public static void main(String[] args) throws Exception {
        String json = """
                {"height":800,"size":"LARGE","title":"image","transparent":"false","uri":"file://image.png","width":600}
                """;
        System.out.println("original json: " + json);
        Image image = JSON.parseObject(json, Image.class);
        System.out.println("image from original json: " + image);
        String jsonString = JSON.toJSONString(image);
        System.out.println("toJSONString: " + jsonString);
        image = JSON.parseObject(jsonString, Image.class);
        System.out.println("image image from toJSONString: " + image);
    }
}
@com.alibaba.fastjson2.annotation.JSONCompiled
public class Image
        implements java.io.Serializable {
    private int height;
    private Size size;
    private String title;
    private String uri;
    private int width;
    private boolean transparent;

    public Image() {
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public void setSize(Size size) {
        this.size = size;
    }

    public String getUri() {
        return uri;
    }

    public String getTitle() {
        return title;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public Size getSize() {
        return size;
    }

    public enum Size {
        SMALL, LARGE
    }

    public boolean isTransparent() {
        return transparent;
    }

    public void setTransparent(boolean transparent) {
        this.transparent = transparent;
    }
}

期待的正确结果

Deserialized from json string successfully.

相关日志输出

original json: {"height":800,"size":"LARGE","title":"image","transparent":"false","uri":"file://image.png","width":600}

image from original json: io.suragreat.issue.fastjson2.graalvm_native.vo.Image@3c49d008 toJSONString: {"height":800,"size":"LARGE","title":"image","transparent":false,"uri":"file://image.png","width":600} Exception in thread "main" com.alibaba.fastjson2.JSONException: syntax error : 102 at com.alibaba.fastjson2.JSONReaderUTF8.readBoolValue(JSONReaderUTF8.java:7550) at com.alibaba.fastjson2.reader.FieldReaderBoolValueMethod.readFieldValue(FieldReaderBoolValueMethod.java:26) at com.alibaba.fastjson2.reader.ObjectReader6.readObject(ObjectReader6.java:389) at com.alibaba.fastjson2.JSON.parseObject(JSON.java:864) at io.suragreat.issue.fastjson2.graalvm_native.App.main(App.java:18)

Root Cause

The root cause is incorrectly Using Unsafe safely in GraalVM Native Image. From the native image building info, there're some warning listed as below:

Warning: RecomputeFieldValue.ArrayBaseOffset automatic substitution failed. The automatic substitution registration was attempted because a call to jdk.internal.misc.Unsafe.arrayBaseOffset(Class) was detected in the static initializer of com.alibaba.fastjson2.util.JDKUtils. Detailed failure reason(s): Could not determine the field where the value produced by the call to jdk.internal.misc.Unsafe.arrayBaseOffset(Class) for the array base offset computation is stored. The call is not directly followed by a field store or by a sign extend node followed directly by a field store. 
Warning: RecomputeFieldValue.FieldOffset automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.objectFieldOffset(Field) was detected in the static initializer of com.alibaba.fastjson2.util.JDKUtils. Detailed failure reason(s): Could not determine the field where the value produced by the call to sun.misc.Unsafe.objectFieldOffset(Field) for the field offset computation is stored. The call is not directly followed by a field store or by a sign extend node followed directly by a field store. 
Warning: RecomputeFieldValue.FieldOffset automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.objectFieldOffset(Field) was detected in the static initializer of com.alibaba.fastjson2.util.JDKUtils. Detailed failure reason(s): Could not determine the field where the value produced by the call to sun.misc.Unsafe.objectFieldOffset(Field) for the field offset computation is stored. The call is not directly followed by a field store or by a sign extend node followed directly by a field store. 
Warning: RecomputeFieldValue.FieldOffset automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.objectFieldOffset(Field) was detected in the static initializer of com.alibaba.fastjson2.util.JDKUtils. Detailed failure reason(s): Could not determine the field where the value produced by the call to sun.misc.Unsafe.objectFieldOffset(Field) for the field offset computation is stored. The call is not directly followed by a field store or by a sign extend node followed directly by a field store. 
Warning: RecomputeFieldValue.FieldOffset automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.objectFieldOffset(Field) was detected in the static initializer of com.alibaba.fastjson2.util.JDKUtils. Detailed failure reason(s): The argument of sun.misc.Unsafe.objectFieldOffset(java.lang.reflect.Field) is not a constant value or a field load that can be constant-folded., Could not determine the field where the value produced by the call to sun.misc.Unsafe.objectFieldOffset(Field) for the field offset computation is stored. The call is not directly followed by a field store or by a sign extend node followed directly by a field store. 
Warning: RecomputeFieldValue.FieldOffset automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.objectFieldOffset(Field) was detected in the static initializer of com.alibaba.fastjson2.util.JDKUtils. Detailed failure reason(s): Could not determine the field where the value produced by the call to sun.misc.Unsafe.objectFieldOffset(Field) for the field offset computation is stored. The call is not directly followed by a field store or by a sign extend node followed directly by a field store. 

According to https://developers.redhat.com/articles/2022/05/09/using-unsafe-safely-graalvm-native-image#unsafe_proves_to_be_unsafe, the static field in com.alibaba.fastjson2.util.JDKUtils which is assigned from UnSafe offset must be marked as final. It works after modification as below:

    static {
        Unsafe unsafe;
        long offset, charOffset;
        try {
            Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafeField.setAccessible(true);
            unsafe = (Unsafe) theUnsafeField.get(null);
            ARRAY_BYTE_BASE_OFFSET = unsafe.arrayBaseOffset(byte[].class);
            ARRAY_CHAR_BASE_OFFSET = unsafe.arrayBaseOffset(char[].class);
        } catch (Throwable e) {
            throw new JSONException("init unsafe error", e);
        }

        UNSAFE = unsafe;
//        ARRAY_BYTE_BASE_OFFSET = offset;
//        ARRAY_CHAR_BASE_OFFSET = charOffset;

        if (ARRAY_BYTE_BASE_OFFSET == -1) {
            throw new JSONException("init JDKUtils error", initErrorLast);
        }
suragreat commented 1 week ago

any updates?