0x01 前言
A fast JSON parser/generator for Java.一个Java语言实现的JSON快速解析/生成器。
官方描述:1
2
3
4
5
6
7
8
9
10
11Fastjson is a Java library that can be used to convert Java Objects into their JSON representation.
It can also be used to convert a JSON string to an equivalent Java object.
Fastjson can work with arbitrary Java objects including pre-existing objects that you do not have source-code of.
Fastjson Goals:
- Provide best performance in server side and android client
- Provide simple toJSONString() and parseObject() methods to convert Java objects to JSON and vice-versa
- Allow pre-existing unmodifiable objects to be converted to and from JSON
- Extensive support of Java Generics
- Allow custom representations for objects
- Support arbitrarily complex objects (with deep inheritance hierarchies and extensive use of generic types)
Fastjson是阿里巴巴开源的Apache顶级项目,在国内开发圈子中被使用广泛,由于它假定有序的解析特性,其相对于Jackson,性能会有一定的优势,不过个人觉得,相对于磁盘、网络IO等时间损耗,这样的提升对于大部分企业来讲,意义并不大。
因为Fastjson在国内被广泛使用,也就是说受众广,影响范围大,那么一但出现安全漏洞,被不法分子利用,将会对企业、用户造成极大损失。对于我们研究安全的人员来讲,研究分析Fastjson的源码,跟踪Fastjson安全漏洞,可以更好的挖掘出潜在的安全隐患,提前消灭它。
我曾经从网络上看到过很多对Fastjson分析的文章,但大部分都是对于新漏洞gadget chain触发的源码debug跟踪,缺少对于一些关键点代码的分析描述,也就是说,我看完之后,该不懂还是不懂,最后时间花出去了,得到的只是一个证明可用的exp…因此,我这篇文章,将针对Fastjson反序列化部分涉及到的关键点代码进行详细的讲解,其中一共四个关键点“词法解析、构造方法选择、缓存绕过、反射调用”,希望大家看完之后,将能完全搞懂Fastjson漏洞触发的一些条件以及原理。
0x02 四个关键点
- 词法解析
- 构造方法选择
- 缓存绕过
- 反射调用
1、词法解析
词法解析是Fastjson反序列化中比较重要的一环,一个json的格式、内容是否能被Fastjson理解,它充当了最重要的角色。
在调用JSON.parse(text)对json文本进行解析时,将使用缺省的默认配置
1 | public static Object parse(String text) { |
DEFAULT_PARSER_FEATURE是一个缺省默认的feature配置,具体每个feature的作用,我这边就不做讲解了,跟这一小节中的词法解析关联不大
1 | public static int DEFAULT_PARSER_FEATURE; |
而如果想使用自定义的feature的话,可以自己或运算配置feature
1 | public static Object parse(String text, int features) { |
而这里,我们也能看到,传入了一个解析配置ParserConfig.getGlobalInstance(),它是一个默认的全局配置,因此,如果我们想要不使用全局解析配置的话,也可以自己构建一个局部的解析配置进行传入,这一系列的重载方法都给我们的使用提供了很大的自由度。
接着,我们可以看到,最终其实都走到这一步,创建DefaultJSONParser类实例,接着对json进行解析
1 | public static Object parse(String text, ParserConfig config, int features) { |
然后我们跟进DefaultJSONParser构造方法中,可以看到其中调用了另一个重载的构造方法,我们可以重点关注第二个参数,也就是JSONScanner,它就是词法解析的具体实现类了
1 | public DefaultJSONParser(final String input, final ParserConfig config, int features){ |
看类注释,可以知道,这个类为了词法解析中的性能提升,做了很多特别的处理1
2
3
4
5
6
7
8//这个类,为了性能优化做了很多特别处理,一切都是为了性能!!!
/**
* @author wenshao[szujobs@hotmail.com]
*/
public final class JSONScanner extends JSONLexerBase {
...
}
在分析该词法解析类之前,我这里列出一些该类以及父类中变量的含义,有助于后续的代码分析:
1 | text:json文本数据 |
可以从JSONScanner构造方法看到,text、len、bp、ch的大概意义,并且对utf-8 bom进行跳过
1 | public JSONScanner(String input, int features){ |
接着在构造方法中,会调用next进行对text中一个一个字符的获取,可以看到bp值初始值为-1,在第一次调用时执行++bp变为0,即开始读取第一个字符的索引
1 | public final char next() { |
再跟进DefaultJSONParser主要构造方法,其中lexer是词法解析器,这里我们跟踪得到其实现为JSONScanner,也就是我们前面所讲的那个,而input就是需要解析的json字符串,config为解析配置,最重要的就是symbolTable,我称之为符号表,它可以根据传入的字符,进而解析知道你想要读取的一段字符串
1 | public DefaultJSONParser(final Object input, final JSONLexer lexer, final ParserConfig config){ |
从上面的if、else流判断中可以知道,当开始头解析时,如果能解析到’{‘或’[‘,就会赋值token,指明当前读到的token类型(在Fastjson中,会对json数据字符串一位一位的提取,然后比对,得出当前位置的词法类型,也即token),接着继续执行next()滑动到下一个字符。如果不能解析到’{‘或’[‘开头,就会执行nextToken(),后续parse也会继续执行nextToken()
nextToken,顾名思义就是下一个token,其中实现逻辑会对字符一个一个的进行一定的解析,判断出下一个token类型
而整个Fastjson反序列化时,就是这样根据不断的next()提取出字符,然后判断当前token类型,接着根据token类型的不同,会有不一样的处理逻辑,表现为根据token类型做一定的数据字符串读取,并根据读取出来的字符串数据,进行反序列化成Java Object
我们回到nextToken中来:
1 | public final void nextToken() { |
可以看到,就是前面所说的,根据当前读到的字符,而选择执行不同的字符串提取逻辑,我们这小节最核心的代码就位于scanString(),当判断当前字符为双引号时,则执行这个方法,我们看一下具体实现
1 | public final void scanString() { |
从代码中可以看到,当下一个字符遇到也是双引号时,就会结束scanString()循环,因为Fastjson认为读取到的字符串为空字符串,接着就是EOI的判断,不过我们这边关心的是’\\‘的处理,因为在java中,只要是硬编码的字符串,对于一些转义字符,都需要使用’\’对其转义,那么’\\‘其实正真就代表则字符’\‘
接着看后面的switch-case处理,可以看出,其实就是对json数据字符串中’\0 \1 \2 \3 \4 \5 \6 \7 \b \t \n \u000B \f \F \r \“ \\ \x \u’等双字节字符的处理,总结一下:
1 | 例: |
其实,对于\x和\u的词法处理,才是反序列化RCE中的核心,也就是我这一节词法解析中最想要讲的内容,我曾经遇到某道CTF题目,它在程序的filter层,对json数据的@type进行的过滤处理,而唯一能绕过它的办法,正恰恰是词法解析中对\x和\u也即16进制、Unicode的处理,通过对字符的十六进制转换或者Unicode处理,我们就可以通过以下的方式进行filter过滤器的绕过,而对于开发人员来说,也可以通过针对这种绕过方式进行filter的加强:
例:1
2@\u0074ype -> @type
@\x74ype -> @type
接着就是执行DefaultJSONParser.parse(),根据上一步中token的识别,进行解析处理
1 | public Object parse(Object fieldName) { |
对象解析,反序列化的利用流程,基本都是走到LBRACE或LBRACKET中,进入对象的解析,而对象解析中,基本都会利用到符号表进行数据的提取:
1 | public final Object parseObject(final Map object, Object fieldName) { |
最后,总结一下:在反序列化RCE中,我们可以利用词法解析中\x\u的十六进制或者Unicode的处理,进行绕过一些检查机制。
2、构造方法选择
构造方法的选择,我这一小节中,主要想讲解的是,在Fastjson反序列化中,针对每个class的特点,到底Fastjson会选择class的哪个构造方法进行反射实例化,到底是否可以不存在无参构造方法。
在上一节:
1 | clazz = config.checkAutoType(typeName, null, lexer.getFeatures()); |
在通过config.checkAutoType后会返回一个class,接着会根据class选择一个ObjectDeserializer,做Java Object的反序列化
而对于ObjectDeserializer的选择,很多class返回的都是一些没有利用价值的ObjectDeserializer:
1 | deserializer |
以及一些根据JSONType注解等不太会存在安全漏洞的条件处理,而对于大部分可利用gadget chains的处理,最终都会走到
1 | com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.reflect.Type) |
接着在其中,构建了JavaBeanInfo,在build方法中,会构建一个JavaBeanInfo对象,其中存储了选择哪个构造方法、字段信息、反射调用哪个方法等等,用于在最后的反射实例化时,做相应的处理
1 | JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz |
跟进JavaBeanInfo.build
1 | JSONType jsonType = TypeUtils.getAnnotation(clazz,JSONType.class); |
可以看到,一开始就会从class中取JSONType注解,根据注解配置去选择参数命名方式,默认是驼峰式
接着会取出class的字段、方法、构造方法等数据,并且判断出class非kotlin实现时,如果构造方法只有一个,则调用getDefaultConstructor获取默认的构造方法
1 | Class<?> builderClass = getBuilderClass(clazz, jsonType); |
从getDefaultConstructor的实现中可以清楚的看到,对于这个构造方法,如果它是无参构造方法或一参(自身类型)构造方法,则就会作为默认构造方法(反序列化对Java Object实例化时反射调用的构造方法),即defaultConstructor1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29static Constructor<?> getDefaultConstructor(Class<?> clazz, final Constructor<?>[] constructors) {
if (Modifier.isAbstract(clazz.getModifiers())) {
return null;
}
Constructor<?> defaultConstructor = null;
for (Constructor<?> constructor : constructors) {
if (constructor.getParameterTypes().length == 0) {
defaultConstructor = constructor;
break;
}
}
if (defaultConstructor == null) {
if (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) {
Class<?>[] types;
for (Constructor<?> constructor : constructors) {
if ((types = constructor.getParameterTypes()).length == 1
&& types[0].equals(clazz.getDeclaringClass())) {
defaultConstructor = constructor;
break;
}
}
}
}
return defaultConstructor;
}
若不存在这样特性的构造方法,则
1 | boolean isInterfaceOrAbstract = clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()); |
从上述代码中可以了解到,若非接口类,并且没有使用JSONCreator注解的话,则会对构造方法进行遍历选择,如果是以下三个class的话,会直接作为构造方法creatorConstructor
1 | org.springframework.security.web.authentication.WebAuthenticationDetails |
而非public的构造方法会被直接跳过。
接着使用com.alibaba.fastjson.util.ASMUtils#lookupParameterNames获取出所有的构造方法参数,若取出的参数为空,则也会直接跳过。
若前面遍历构造方法时,已有creatorConstructor选择,以及asm取出的参数数量<=构造方法参数数量,则也会直接跳过。
也就是说,除非是非公有public的,否则必然会选择一个creatorConstructor。
1 | defaultConstructor:无参和一参(自身类型入参)构造方法 |
总结的来讲:若可以找到defaultConstructor,则不再遍历选择creatorConstructor,否则必须遍历查找creatorConstructor。
3、缓存绕过
缓存绕过,这是什么意思?我们上一小节已经详细的描述并总结了构造方法的选择逻辑。其中构造方法的选择分为defaultConstructor和creatorConstructor
我们这一节主要分析的关键点:缓存绕过,位于com.alibaba.fastjson.parser.ParserConfig#checkAutoType(java.lang.String, java.lang.Class<?>, int),它是在json数据反序列化时,通过@type指定class后,对class是否可被反序列化进行检查,其中检查包括黑名单、白名单、构造方法等
我们跟进com.alibaba.fastjson.parser.ParserConfig#checkAutoType(java.lang.String, java.lang.Class<?>, int):
1 | if (typeName == null) { |
可以看到,一开始的地方,会对typeName做一定的检查,typeName是我们传入json数据@type这个key对应的值value
1 | final boolean expectClassFlag; |
接着是对一些期望class的判断,若不是期望中反序列化指定的class,后续黑白名单的检查会不管是否启用autoTypeSupport。
1 | String className = typeName.replace('$', '.'); |
再接着,会对className的前两位字符进行判断是否允许,然后会二分查找内部白名单INTERNAL_WHITELIST_HASHCODES,若不在内部白名单内,并且开启了autoTypeSupport或者是预期以外的class,则会对className后面的字符继续进行hash处理后与外部白名单、黑名单进行判断,决定其是否被支持反序列化。
1 | if (clazz == null) { |
从上面的代码中,我们可以看到有好几个if流程从jvm缓存中获取class,也即会对class进行一定的判断,决定是否从缓存map中加载,我们这一节重点关注的其实是TypeUtils.getClassFromMapping:
1 | if (clazz == null) { |
跟进TypeUtils.getClassFromMapping代码实现,可以看到,其具体是从mappings缓存中获取class
1 | public static Class<?> getClassFromMapping(String className){ |
接着会判断class是否在内部白名单内,若在白名单内,会直接通过检查,返回class
1 | if (internalWhite) { |
跟进TypeUtils.loadClass,可以看到第三个参数true,决定了在其方法实现中是否会对查找出来的class进行缓存到mappings
1 | public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) { |
下一步,可以看到,又是一段黑白名单的检查代码,不过这次是autoTypeSupport不启用的情况下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25if (!autoTypeSupport) {
long hash = h3;
for (int i = 3; i < className.length(); ++i) {
char c = className.charAt(i);
hash ^= c;
hash *= PRIME;
if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {
throw new JSONException("autoType is not support. " + typeName);
}
// white list
if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
}
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}
接着,在这段代码中通过asm对其class进行visit,取出JsonType注解信息
1 | boolean jsonType = false; |
而从后续代码中也可以了解到,若到这一步,class还是null的时候,就会对其是否注解了JsonType、是否期望class、是否开启autotype进行判断。若判断通过,然后会判断是否开启autotype或是否注解了JsonType,从而觉得是否会在加载class后,对其缓存到mappings这个集合中,那也就是说,我只要开启了autoType的话,在这段逻辑就会把class缓存道mappings中
1 | if (clazz == null && (autoTypeSupport || jsonType || expectClassFlag)) { |
上面这一块是一个很关键的地方,也是我这一小节缓存绕过的主要核心
最后,也就是我们需要去绕过的地方了,像一般大部分情况下,我们基本不可能找到注解有JsonType的class的gadget chains,所以,这一步中对jsonType判断,然后缓存class到mappings基本就没什么利用价值了。但这块逻辑中,我们需要注意的其实是JavaBeanInfo在build后,对其creatorConstructor的判断
1 | if (clazz != null) { |
从creatorConstructor和autoTypeSupport的判断流程中,我们可以得知,只要autoTypeSupport为true,并且creatorConstructor(上一小节就是描述构造方法的选择,这里判断的构造方法是第二种选择)不为空,则会抛出异常,而后面的!autoTypeSupport判断,也表示了,就算上一步通过设置autoTypeSupport为true可以绕过,但是最终也避免不了它抛出异常的制裁:
1 | if (!autoTypeSupport) { |
那怎么办呢?这时候就得看前面的代码了,我前面也说了,在对黑白名单进行一轮检查后的时候,会有这个判断:
1 | if (clazz == null) { |
从mappings中直接获取,接着在后面判断道class不为空时,直接就返回了,从而提前结束该方法执行,绕过构造方法creatorConstructor和autoTypeSupport的判断
1 | if (clazz != null) { |
那我们怎么才能从缓存中获取到class呢?答案其实前面也说了:1
2
3
4if (clazz == null && (autoTypeSupport || jsonType || expectClassFlag)) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
}
对,没错,就是这里,我们只要开启了autoTypeSupport,绕后通过两次反序列化,在第一次反序列化时,虽然最后会抛出异常,但是在抛异常前,做了上述代码中的缓存class到mappings的处理,那么,在第二次反序列化该class的时候,我们就可以顺利的从缓存中取出了,从而绕过后面的判断。
4、反射调用
反射调用,就是fastjson反序列化的最后一个阶段了,当经历了前面:词法解析、构造方法选择、缓存绕过阶段之后,我们离RCE就差最后的一步了,也就是反射调用,从而触发gadget chain的执行,最终实现RCE。
接着,又回到DefaultJSONParser.parseObject来,也就是第2小节构造方法选择部分
1 | ObjectDeserializer deserializer = config.getDeserializer(clazz); |
前面也说了,大部分可利用的gadget chain,config.getDeserializer(clazz)最终都会走到
1 | com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.reflect.Type) |
而反射调用,是选择setter还是getter方法调用,亦或者是直接反射field设值,它需要一系列的判断处理,最终确定下来在JavaBeanDeserializer中执行deserialze时,到底会做什么样的反射调用处理
我们跟进JavaBeanInfo.build,前面一大段,我们在讲构造方法选择的时候已经简单讲过了,但是我们并没讲一个小地方,就是FieldInfo的创建和添加1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33if (creatorConstructor != null && !isInterfaceOrAbstract) { // 基于标记 JSONCreator 注解的构造方法
...
if (types.length > 0) {
...
FieldInfo fieldInfo = new FieldInfo(fieldName, clazz, fieldClass, fieldType, field,
ordinal, serialzeFeatures, parserFeatures);
add(fieldList, fieldInfo);
...
}
//return new JavaBeanInfo(clazz, builderClass, null, creatorConstructor, null, null, jsonType, fieldList);
} else if ((factoryMethod = getFactoryMethod(clazz, methods, jacksonCompatible)) != null) {
...
Field field = TypeUtils.getField(clazz, fieldName, declaredFields);
FieldInfo fieldInfo = new FieldInfo(fieldName, clazz, fieldClass, fieldType, field,
ordinal, serialzeFeatures, parserFeatures);
add(fieldList, fieldInfo);
...
} else if (!isInterfaceOrAbstract) {
...
if (paramNames != null
&& types.length == paramNames.length) {
...
FieldInfo fieldInfo = new FieldInfo(paramName, clazz, fieldClass, fieldType, field,
ordinal, serialzeFeatures, parserFeatures);
add(fieldList, fieldInfo);
...
}
}
在省略大部分无关代码后,可以看到,对于这三种情况下的处理,最终都是实例化FieldInfo,然后直接调用add添加到集合fieldList中来,但是细心去看FieldInfo重载的构造方法可以发现,它存在多个构造方法,其中就有入参method的构造方法:
1 | public FieldInfo(String name, // |
1 | public FieldInfo(String name, // |
1 | public FieldInfo(String name, // |
这种构造方法意味着什么?在后面执行JavaBeanDeserializer.deserialze时,会发现,具有method入参的字段,很有可能会触发方法的执行,从而可以触发gadget chain的执行。
接着,后面就是一串惆怅的代码,无非就是根据setter方法名称智能提取出field名字…,其中会对所有的方法进行两次的遍历,我这边简单总结一下:
第一遍
1
2
3
4
5
6
7
81. 静态方法跳过
2. 返回值类型不为Void.TYPE和自身class类型的方法跳过
3. 获取JSONField注解,确定字段field名称,然后和方法添加到集合中
4. 没有JSONField则判断方法名长度是否大于4,不大于4则跳过
5. 判断是否set前缀,不是则跳过
6. 根据setter方法名从第四个字符开始确定字段field名称(需把第一个字符转小写),若是boolean类型,则需把字段第一个字符转大写,然后前面拼接is
7. 根据字段名获取到字段Field后,判断是否注解了JSONField,获取JSONField注解,确定字段field名称,然后和方法添加到集合中
8. 根据setter方法确定的字段名添加到集合第二遍
1 | 1. 判断方法名长度是否大于4,不大于4则跳过 |
以上就是总结,从这些总结,我们就不难分析,fastjson反序列化时,class到底哪个方法能被触发。
最后,对于这些添加到集合fieldList中的FieldInfo,会在JavaBeanDeserializer.deserialze中被处理
1 | protected <T> T deserialze(DefaultJSONParser parser, // |
从上述代码可以看到,配对”@type”:”…”之后,如果下一个token不为”}”,即JSONToken.RBRACE,则获取反序列化器进行反序列化,根据前面扫描Field得到的信息以及json后续的key-value进行反序列化,如果下一个token为”}”,则直接反射实例化返回
判断下一个token为”[“,即JSONToken.LBRACKET,则进行数组处理1
2
3
4
5
6
7
8
9
10if (token == JSONToken.LBRACKET) {
final int mask = Feature.SupportArrayToBean.mask;
boolean isSupportArrayToBean = (beanInfo.parserFeatures & mask) != 0 //
|| lexer.isEnabled(Feature.SupportArrayToBean) //
|| (features & mask) != 0
;
if (isSupportArrayToBean) {
return deserialzeArrayMapping(parser, type, fieldName, object);
}
}
调用构造方法
1 | if (beanInfo.creatorConstructor != null) { |
最后,通过FieldDeserializer对字段进行反序列化处理,其中,会利用到FieldInfo前面构建时,收集到的信息,例如method、getOnly等,进行判断是否调用某些方法
1 | FieldDeserializer fieldDeserializer = getFieldDeserializer(entry.getKey()); |
可以看到,对于method不为空的fieldInfo,若getOnly为false,则直接反射执行method,若getOnly为true,也就是只存在对应字段field的getter,而不存在setter,则会对其method的返回类型进行判断,若符合,才会进行反射执行该method1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47Method method = fieldInfo.method;
if (method != null) {
if (fieldInfo.getOnly) {
if (fieldInfo.fieldClass == AtomicInteger.class) {
AtomicInteger atomic = (AtomicInteger) method.invoke(object);
if (atomic != null) {
atomic.set(((AtomicInteger) value).get());
}
} else if (fieldInfo.fieldClass == AtomicLong.class) {
AtomicLong atomic = (AtomicLong) method.invoke(object);
if (atomic != null) {
atomic.set(((AtomicLong) value).get());
}
} else if (fieldInfo.fieldClass == AtomicBoolean.class) {
AtomicBoolean atomic = (AtomicBoolean) method.invoke(object);
if (atomic != null) {
atomic.set(((AtomicBoolean) value).get());
}
} else if (Map.class.isAssignableFrom(method.getReturnType())) {
Map map = (Map) method.invoke(object);
if (map != null) {
if (map == Collections.emptyMap()
|| map.getClass().getName().startsWith("java.util.Collections$Unmodifiable")) {
// skip
return;
}
map.putAll((Map) value);
}
} else {
Collection collection = (Collection) method.invoke(object);
if (collection != null && value != null) {
if (collection == Collections.emptySet()
|| collection == Collections.emptyList()
|| collection.getClass().getName().startsWith("java.util.Collections$Unmodifiable")) {
// skip
return;
}
collection.clear();
collection.addAll((Collection) value);
}
}
} else {
method.invoke(object, value);
}
}
而对于method为空的情况,根本就不可能对method进行反射调用,除了构建实例时选择的构造方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49} else {
final Field field = fieldInfo.field;
if (fieldInfo.getOnly) {
if (fieldInfo.fieldClass == AtomicInteger.class) {
AtomicInteger atomic = (AtomicInteger) field.get(object);
if (atomic != null) {
atomic.set(((AtomicInteger) value).get());
}
} else if (fieldInfo.fieldClass == AtomicLong.class) {
AtomicLong atomic = (AtomicLong) field.get(object);
if (atomic != null) {
atomic.set(((AtomicLong) value).get());
}
} else if (fieldInfo.fieldClass == AtomicBoolean.class) {
AtomicBoolean atomic = (AtomicBoolean) field.get(object);
if (atomic != null) {
atomic.set(((AtomicBoolean) value).get());
}
} else if (Map.class.isAssignableFrom(fieldInfo.fieldClass)) {
Map map = (Map) field.get(object);
if (map != null) {
if (map == Collections.emptyMap()
|| map.getClass().getName().startsWith("java.util.Collections$Unmodifiable")) {
// skip
return;
}
map.putAll((Map) value);
}
} else {
Collection collection = (Collection) field.get(object);
if (collection != null && value != null) {
if (collection == Collections.emptySet()
|| collection == Collections.emptyList()
|| collection.getClass().getName().startsWith("java.util.Collections$Unmodifiable")) {
// skip
return;
}
collection.clear();
collection.addAll((Collection) value);
}
}
} else {
if (field != null) {
field.set(object, value);
}
}
}
至此,四个关键点得分析就此结束!