前言

本文主要讲述了一个具有"随机性"的反序列化错误!

Fastjson作为一款高性能的JSON序列化框架,使用场景众多,不过也存在一些潜在的bug和不足。本文主要讲述了一个具有"随机性"的反序列化错误!

问题代码

为了清晰地描述整个报错的来龙去脉,将相关代码贴出来,同时也为了可以本地执行,看一下实际效果。

StewardTipItem

package test;
import java.util.List;
public class StewardTipItem {
    private Integer type;
    private List<String> contents;
    public StewardTipItem(Integer type, List<String> contents) {
        this.type = type;
        this.contents = contents;
    }
}

StewardTipCategory

反序列化时失败,此类有两个特殊之处:

  • 返回StewardTipCategory的build方法(忽略返回null值)。
  • 构造函数『C1』Map<Integer, List> items参数与List items属性同名,但类型不同!
package test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class StewardTipCategory {
    private String category;
    private List<StewardTipItem> items;
    public StewardTipCategory build() {
        return null;
    }
    //C1 下文使用C1引用该构造函数
    public StewardTipCategory(String category, Map<Integer,List<String>> items) {          
        List<StewardTipItem> categoryItems = new ArrayList<>();
    for (Map.Entry<Integer, List<String>> item : items.entrySet()) { 
        StewardTipItem tipItem = new StewardTipItem(item.getKey(), item.getValue());                   categoryItems.add(tipItem);
    }
    this.items = categoryItems;
    this.category = category; 
}
    // C2 下文使用C2引用该构造函数
    public StewardTipCategory(String category, List<StewardTipItem> items) {        
        this.category = category;
        this.items = items;
    }
    public String getCategory() {
        return category;
    }
    public void setCategory(String category) {
        this.category = category;
    }
    public List<StewardTipItem> getItems() {
        return items; 
    }
    public void setItems(List<StewardTipItem> items) {
        this.items = items; 
    }
}

StewardTip

package test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class StewardTip {
    private List<StewardTipCategory> categories;
    public StewardTip(Map<String, Map<Integer, List<String>>> categories) {          
        List<StewardTipCategory> tipCategories = new ArrayList<>();
        for (Map.Entry<String, Map<Integer, List<String>>> category : categories.entrySet()) {             StewardTipCategory tipCategory = new StewardTipCategory(category.getKey(), category.getValue());
            tipCategories.add(tipCategory);
        }
        this.categories = tipCategories; 
    }
    public StewardTip(List<StewardTipCategory> categories) {
        this.categories = categories;
    }
    public List<StewardTipCategory> getCategories() { 
        return categories;
    }
    public void setCategories(List<StewardTipCategory> categories) {
        this.categories = categories;
    }
}

JSON字符串

{
    "categories":[
        {
             "category":"工艺类",
             "items":[
                 {
                     "contents":[
                         "工艺类-提醒项-内容1", 
                         "工艺类-提醒项-内容2"
                     ],
                     "type":1
                }, 
                { 
                     "contents":[ 
                         "工艺类-疑问项-内容1" 
                     ], 
                     "type":2
                }
            ]
        }
    ]
}

FastJSONTest

package test;
import com.alibaba.fastjson.JSONObject;
public class FastJSONTest {
    public static void main(String[] args) {
        String tip = "{"categories":[{"category":"工艺类","items":[{"contents":["工艺类-提醒项-内容1","工艺类-提醒项-内容2"],"type":1},{"contents":["工艺类-疑问项-内容1"],"type":2}]}]}";        
        try {
            JSONObject.parseObject(tip, StewardTip.class); 
        } catch (Exception e) {
            e.printStackTrace(); 
        }
    }
}

堆栈信息

当执行FastJSONTest的main方法时报错:

com.alibaba.fastjson.JSONException: syntax error, expect {, actual [
    at com.alibaba.fastjson.parser.deserializer.MapDeserializer.parseMap(MapDeserializer.java:228)
    at com.alibaba.fastjson.parser.deserializer.MapDeserializer.deserialze(MapDeserializer.java:67)  
    at com.alibaba.fastjson.parser.deserializer.MapDeserializer.deserialze(MapDeserializer.java:43)  
    at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:85)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:838)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:288)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:284)
    at com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer.parseArray(ArrayListTypeFieldDeserializer.java:181)
    at com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer.parseField(ArrayListTypeFieldDeserializer.java:69) 
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:838)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:288)
    at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:672)  at com.alibaba.fastjson.JSON.parseObject(JSON.java:396) 
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:300)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:573)
    at test.FastJSONTest.main(FastJSONTest.java:17)

问题排查

排查过程有两个难点:

  • 不能根据报错信息得到异常时JSON字符串的key,position或者其他有价值的提示信息。
  • 报错并不是每次执行都会发生,存在随机性,执行十次可能报错两三次,没有统计失败率。

经过多次执行之后还是找到了一些蛛丝马迹!下面结合源码对整个过程进行简单地叙述,最后也会给出怎么能在报错的时候debug到代码的方法。

JavaBeanInfo:285行

clazz是StewardTipCategory.class的情况下,提出以下两个问题:

Q1:Constructor[] constructors数组的返回值是什么?

Q2:constructors数组元素的顺序是什么?

参考java.lang.Class#getDeclaredConstructors的注释,可得到A1:

  • A1

public test.StewardTipCategory(java.lang.String,java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>)『C1』

public test.StewardTipCategory(java.lang.String,java.util.List<test.StewardTipItem>)『C2』

  • A2

build()方法,C1构造函数,C2构造函数三者在Java源文件的顺序决定了constructors数组元素的顺序!

下表是经过多次实验得到的一组数据,因为是手动触发,并且次数较少,所以不能保证100%的准确性,只是一种大概率事件。

java.lang.Class#getDeclaredConstructors底层实现是native getDeclaredConstructors0,JVM的这部分代码没有去阅读,所以目前无法解释产生这种现象的原因。

数组元素顺序
build() C1 C2 随机
C1 build() C2 C2,C1
C1 C2 build() C2,C1
build() C2 C1 随机
C2 build() C1 C1,C2
C2 C1 build() C1,C2
C1   C2 C2,C1
C2 C1 C1,C2  

正是因为java.lang.Class#getDeclaredConstructors返回数组元素顺序的随机性,才导致反序列化失败的随机性!

  • [C2,C1]反序列化成功!
  • [C1,C2]反序列化失败!

[C1,C2]顺序下探寻反序列化失败时代码执行的路径。

JavaBeanInfo:492行

com.alibaba.fastjson.util.JavaBeanInfo#build()方法体代码量比较大,忽略执行路径上的无关代码。\

  • [C1,C2]顺序下代码会执行到492行,并执行两次(StewardTipCategory#category, StewardTipCategory#items各执行一次)。
  • 结束后创建一个com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer。

JavaBeanDeserializer:49行

JavaBeanDeserializer两个重要属性:

private final FieldDeserializer[]   fieldDeserializers;

protected final FieldDeserializer[] sortedFieldDeserializers;

反序列化test.StewardTipCategory#items时fieldDeserializers的详细信息。

com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializercom.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#fieldValueDeserilizer(属性值null,运行时会根据fieldType获取具体实现类)com.alibaba.fastjson.util.FieldInfo#fieldType(java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>)

创建完成执行

com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object, java.lang.Object, int, int[])

JavaBeanDeserializer:838行

DefaultFieldDeserializer:53行

com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.Class<?>, java.lang.reflect.Type)根据字段类型设置

com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#fieldValueDeserilizer的具体实现类。

DefaultFieldDeserializer:34行

test.StewardTipCategory#items属性的实际类型是List。

反序列化时根据C1构造函数得到的fieldValueDeserilizer的实现类是com.alibaba.fastjson.parser.deserializer.MapDeserializer。

执行

com.alibaba.fastjson.parser.deserializer.MapDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object)时报错。

MapDeserializer:228行

JavaBeanDeserializer:838行

java.lang.Class#getDeclaredConstructors返回[C2,C1]顺序,反序列化时根据C2构造函数得到的fieldValueDeserilizer的实现类是

com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer,反序列化成功。

问题解决

代码

  • 删除C1构造函数,使用其他方式创建StewardTipCategory。
  • 修改C1构造函数参数名称,类型,避免误导Fastjson。

调试

package test;
import com.alibaba.fastjson.JSONObject;
import java.lang.reflect.Constructor;
public class FastJSONTest {
    public static void main(String[] args) { 
        Constructor<?>[] declaredConstructors = StewardTipCategory.class.getDeclaredConstructors(); 
        // if true must fail!
       if ("public test.StewardTipCategory(java.lang.String,java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>)".equals(declaredConstructors[0].toGenericString())) {                 
           String tip = "{"categories":[{"category":"工艺类","items":[{"contents":["工艺类-提醒项-内容1","工艺类-提醒项-内容2"],"type":1},{"contents":["工艺类-疑问项-内容1"],"type":2}]}]}";                   
           try { 
                JSONObject.parseObject(tip, StewardTip.class);
            } catch (Exception e) {  
                e.printStackTrace();
            }
        }
    }
}

总结

开发过程中尽量遵照规范/规约,不要特立独行

StewardTipCategory构造函数C1方法签名明显不是一个很好的选择,方法体除了属性赋值,还做了一些额外的类型/数据转换,也应该尽量避免。

专业有深度

开发人员对于使用的技术与框架要有深入的研究,尤其是底层原理,不能停留在使用层面。一些不起眼的事情可能导致不可思议的问题:java.lang.Class#getDeclaredConstructors。

Fastjson

框架实现时要保持严谨,报错信息尽可能清晰明了,StewardTipCategory反序列化失败的原因在于,fastjson只检验了属性名称,构造函数参数个数而没有进一步校验属性类型。

<<重构:改善既有代码的设计>>提倡代码方法块尽量短小精悍,Fastjson某些模块的方法过于臃肿。

以上就是Fastjson反序列化随机性失败示例详解的详细内容,更多关于Fastjson反序列化随机性的资料请关注Devmax其它相关文章!

Fastjson反序列化随机性失败示例详解的更多相关文章

  1. 详解PHP序列化和反序列化原理

    本篇文章给大家分享了下PHP反序列化漏洞系列之PHP序列化和反序列化原理的相关知识,有这方面需要的朋友参考学习下吧。

  2. fastjson转换对象实体@JsonProperty不生效问题及解决

    这篇文章主要介绍了fastjson转换对象实体@JsonProperty不生效问题及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. java安全fastjson1.2.24反序列化TemplatesImpl分析

    这篇文章主要介绍了java安全fastjson1.2.24反序列化TemplatesImpl分析,fastjson是alibaba开源的一个用于处理json数据格式的解析库,它支持将java对象解析成json字符串格式的数据,也可以将json字符串还原成java对象

  4. Fastjson反序列化随机性失败示例详解

    这篇文章主要为大家介绍了Fastjson反序列化随机性失败示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  5. PHP 序列化和反序列化函数实例详解

    这篇文章主要介绍了PHP 序列化和反序列化函数,需要的朋友可以参考下

  6. 详解php反序列化之字符逃逸法

    这篇文章主要为大家详细介绍了php反序列化之字符逃逸法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

  7. 详解PHP反序列化漏洞的原理及示例

    PHP反序列化漏洞也叫PHP对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。本文将详细讲讲PHP反序列化漏洞的原理及示例,感兴趣的可以了解一下

  8. PHP中的随机性 你觉得自己幸运吗?

    PHP中的随机性,你觉得自己幸运吗?

  9. Alibaba Fastjson之超好用的JOSN解析库

    这篇文章主要介绍了Alibaba Fastjson之超好用的JOSN解析库,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  10. php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia)

    这篇文章主要介绍了0CTF-2016-piapiapia(php反序列化长度变化尾部字符串逃逸),本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部