fastjson2 icon indicating copy to clipboard operation
fastjson2 copied to clipboard

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

Open suragreat opened this issue 1 year ago • 1 comments

问题描述

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

环境信息

  • OS信息: macOS 14.7 (23H124)
  • JDK信息: Java(TM) SE Runtime Environment Oracle GraalVM 17.0.12+8.1 (build 17.0.12+8-LTS-jvmci-23.0-b41)
  • 版本信息:2.0.53

重现步骤

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 avatar Oct 17 '24 01:10 suragreat

any updates?

suragreat avatar Nov 11 '24 02:11 suragreat

fatjson2 2.57 版本 和 graalvm21版本 , deserialize boolean 字段还是错误的, 导致rocketmq反序列化失败, 自己也简单设置了一个boolean字段的类,反序列化失败,堆栈信息是:

org.springframework.messaging.MessagingException: syntax error : 102
        at org.apache.rocketmq.spring.core.RocketMQTemplate.sendOneWay(RocketMQTemplate.java:1083)
        at org.apache.rocketmq.spring.core.RocketMQTemplate.sendOneWay(RocketMQTemplate.java:1095)
        at com.huyu.testgraalvmlogmaven.controller.TestController.test(TestController.java:43)
        at [email protected]/java.lang.reflect.Method.invoke(Method.java:580)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
        at [email protected]/java.lang.Thread.runWith(Thread.java:1596)
        at [email protected]/java.lang.Thread.run(Thread.java:1583)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:896)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:872)
Caused by: com.alibaba.fastjson.JSONException: syntax error : 102
        at com.alibaba.fastjson.JSON.parseObject(JSON.java:866)
        at org.apache.rocketmq.remoting.protocol.RemotingSerializable.fromJson(RemotingSerializable.java:60)
        at org.apache.rocketmq.remoting.protocol.RemotingSerializable.decode(RemotingSerializable.java:44)
        at org.apache.rocketmq.client.impl.MQClientAPIImpl.getTopicRouteInfoFromNameServer(MQClientAPIImpl.java:1991)
        at org.apache.rocketmq.client.impl.MQClientAPIImpl.getTopicRouteInfoFromNameServer(MQClientAPIImpl.java:1969)
        at org.apache.rocketmq.client.impl.factory.MQClientInstance.updateTopicRouteInfoFromNameServer(MQClientInstance.java:798)
        at org.apache.rocketmq.client.impl.factory.MQClientInstance.updateTopicRouteInfoFromNameServer(MQClientInstance.java:576)
        at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.tryToFindTopicPublishInfo(DefaultMQProducerImpl.java:900)
        at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.sendDefaultImpl(DefaultMQProducerImpl.java:750)
        at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.sendOneway(DefaultMQProducerImpl.java:1212)
        at org.apache.rocketmq.client.producer.DefaultMQProducer.sendOneway(DefaultMQProducer.java:558)
        at org.apache.rocketmq.spring.core.RocketMQTemplate.sendOneWay(RocketMQTemplate.java:1080)
        ... 52 more
Caused by: 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.ObjectReader5.readObject(ObjectReader5.java:370)
        at com.alibaba.fastjson2.reader.FieldReaderList.readFieldValue(FieldReaderList.java:115)
        at com.alibaba.fastjson2.reader.ObjectReader5.readObject(ObjectReader5.java:364)
        at com.alibaba.fastjson.JSON.parseObject(JSON.java:853)

huyu-tom avatar Jun 27 '25 17:06 huyu-tom

https://github.com/alibaba/fastjson2/releases/tag/2.0.58 问题已修复,请用新版本

wenshao avatar Jul 30 '25 05:07 wenshao