[BUG] Failed to deserialize boolean field with false value in GraalVM native image.
问题描述
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);
}
any updates?
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)
https://github.com/alibaba/fastjson2/releases/tag/2.0.58 问题已修复,请用新版本