[alibaba/fastjson]1.2.76中BugFix#3693的修复代码又引入了新的Bug!

2024-09-06 341 views
7

https://github.com/alibaba/fastjson/issues/3693

上面的 #3639 提出了一个bug,即通过@JSONField#deserializeUsing属性指定的自定义反序列化器被后续的代码覆盖,并在1.2.76中进行了修复,然而我们在使用升级后的1.2.76时发现1.2.75中没有问题的代码出现报错,经定位是该Issue的解决方案引入了新的Bug。

我们的代码是构造了一个ClassA<ClassB<String>>这种样式的类,ClassA<T>的声明中有一个字段T data,实际类型是ClassB<String>,现在1.2.76的修复方案,虽然确保了@JSONField#deserializeUsing属性指定的自定义反序列化器具有最高的优先级,但是在我们的场景下,并没有使用@JSONField注解,原本ClassAT data字段的反序列化器应该是针对运行时实际类型ClassB<String>创建的,现在却获取到的是一个普通的Object的通用反序列化器。

具体代码如下,1.2.76中:

    public ObjectDeserializer getFieldValueDeserilizer(ParserConfig config) {
        if (fieldValueDeserilizer == null) {
            JSONField annotation = fieldInfo.getAnnotation();
            if (annotation != null && annotation.deserializeUsing() != Void.class) {
                Class<?> deserializeUsing = annotation.deserializeUsing();
                try {
                    fieldValueDeserilizer = (ObjectDeserializer) deserializeUsing.newInstance();
                } catch (Exception ex) {
                    throw new JSONException("create deserializeUsing ObjectDeserializer error", ex);
                }
            } else {
                fieldValueDeserilizer = config.getDeserializer(fieldInfo.fieldClass, fieldInfo.fieldType);
            }
        }

        return fieldValueDeserilizer; // 这个成员变量和返回值不为空
    }

这个方法无论成员变量fieldValueDeserilizer初始的状态是否为null,该方法执行后,成员变量fieldValueDeserilizer都会被转为非null的值,考虑了通过@JSONField注解自定义反序列化器的场景。

但是在下面的方法中:

@Override
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
        if (this.fieldValueDeserilizer == null) {
            getFieldValueDeserilizer(parser.getConfig());
        }

        ObjectDeserializer fieldValueDeserilizer = this.fieldValueDeserilizer;
        Type fieldType = fieldInfo.fieldType;
        if (objectType instanceof ParameterizedType) {
            ParseContext objContext = parser.getContext();
            if (objContext != null) {
                objContext.type = objectType;
            }
            if (fieldType != objectType) {
                fieldType = FieldInfo.getFieldType(this.clazz, objectType, fieldType);
                if (fieldValueDeserilizer == null) { // 该处修改导致下面一行失效
                    fieldValueDeserilizer = parser.getConfig().getDeserializer(fieldType); // 代码失效,原有逻辑无法正确执行
                }
            }
        }

1.2.76中的67行进行了一个null判断,但是ObjectDeserializer fieldValueDeserilizer = this.fieldValueDeserilizer即局部变量fieldValueDeserilizer正是成员变量this.fieldValueDeserilizer,而后者如上面所说的,在该方法调用上面的getFieldValueDeserilizer之后永远不会为null,所以导致68行代码不会被执行,而之前的版本能够处理这种嵌套的场景,原因就在于68行获取了一个与实际运行时类型匹配的反序列化器,现在这样的修复方案使得68行代码失效了,从而嵌套泛型的场景下失效,将给现有生产代码造成巨大影响

回答

9

提供下测试用例?

9

我们本身的场景是对Spring MVC的@RequestBody的处理逻辑进行了扩展,里面的例子跟Spring框架绑定太严,不便于操作,就写了一个简单的,问题同样存在:

package com.dw.infinite.junit5;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.reflect.TypeToken;
import lombok.Data;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Type;

public class FastJsonTest {
    @Data
    static class TestA<T> {
        int a1;
        T a2;
    }
    @Data
    static class TestB<T> {
        String b1;
        T b2;
    }

    @Test
    void test() throws JsonProcessingException {
        String json = "{\"a1\":99,\"a2\":{\"b1\":\"b1\",\"b2\":\"b2\"}}";

        TestA<?> ret1 = null;
        TestA<?> ret2 = null;

        TypeToken<TestB<String>> tt = new TypeToken<TestB<String>>() {};
        Type bType = tt.getType();
        Type realType = new ParameterizedTypeImpl(new Type[]{ bType }, TestA.class, TestA.class);

        JSONObject jo = JSONObject.parseObject(json);
        ret1 = jo.toJavaObject(realType);
        ret2 = JSON.parseObject(json, realType);

        System.out.println(ret1.getA2().getClass().getName());
        System.out.println(ret2.getA2().getClass().getName());
    }
}

同一段代码,1.2.76的输出是,通过调试也能看到,这个结果跟上一个版本不一致,也不是我们要的结果:

com.alibaba.fastjson.JSONObject
com.alibaba.fastjson.JSONObject

1.2.75的输出是正确的,通过调试跟进去看也能发现类型是对的:

com.dw.infinite.junit5.FastJsonTest$TestB
com.dw.infinite.junit5.FastJsonTest$TestB

另外你看我贴的代码,1.2.76里面的代码实现是有点问题的,这块具体代码我不是特别清楚,但是看

                if (fieldValueDeserilizer == null) { // 该处修改导致下面一行失效,感觉这个值可能恒不为null
                    fieldValueDeserilizer = parser.getConfig().getDeserializer(fieldType); // 代码失效,原有逻辑无法正确执行
                }

这一块觉得代码块里的代码可能会失效,实际上我们的场景中1.2.76的代码就失效了。

8

提供下测试用例?

已提供,请参考。

9

我也遇到了这样的问题,且由于受到TypeReference的缓存影响,该问题十分隐蔽且难以复现 测试用例如下,请参考~

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import lombok.Data;
import org.junit.Test;

import java.lang.reflect.Type;
import java.util.List;

public class FastJsonTest {
    @Data
    static class TestA<T> {
        T a;
    }

    @Data
    static class TestB {
        String b;
    }

    private final static String json = "{\"a\":[{\"b\":\"b\"}]}";

    /**
     * DefaultFieldDeserializer的parseField方法在1.2.76中67行引入了一个null判断,修复了@JSONField注解自定义反序列化器失效的问题#3693,
     * 但对于没有使用这个注解自定义反序列化器的情况,原本是通过68行获取实际运行时类型fieldType匹配的反序列化器,
     * 由于通过55行getFieldValueDeserilizer始终能获取到默认的普通Object通用反序列化器,null判断始终为false使这个逻辑失效了,
     * 从而导致反序列化使用了默认的普通Object通用反序列化器,将list反序列化为JSONArray,将对象反序列化为JSONObject,
     * 后续逻辑处理就会抛出java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to ...
     *
     * @see com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer
     */
//    @Test
    public void testError() {
        System.out.println("---------------testError---------------");
        ParameterizedTypeImpl inner = new ParameterizedTypeImpl(new Type[]{TestB.class}, List.class, List.class);
        ParameterizedTypeImpl outer = new ParameterizedTypeImpl(new Type[]{inner}, TestA.class, TestA.class);
        JSONObject jo = JSONObject.parseObject(json);
        //为了打印出b的className这里将返回值改为TestA<List<?>>,否则会抛出ClassCastException
        TestA<List<?>> ret = jo.toJavaObject(outer);

        System.out.println("a class name: " + ret.getA().getClass().getName());
        System.out.println("b class name: " + ret.getA().get(0).getClass().getName());
    }

    /**
     * 且由于受到TypeReference的缓存影响,该问题十分隐蔽且难以复现
     * 如果先调用错误方法,后续即使调用相同泛型的正确方法也会全部解析报错
     * 如果先调用正确方法,后续调用相同泛型的正确方法就不会出现问题
     *
     * @see com.alibaba.fastjson.TypeReference
     */
    @Test
    public void testErrorFirst() {
        testError();
        testOk();
    }

    @Test
    public void testOkFirst() {
        testOk();
        testError();
        testOk();
    }

//    @Test
    public void testOk() {
        testOk1();
        testOk2();
    }

//    @Test
    public void testOk1() {
        System.out.println("---------------testOk1---------------");
        TestA<List<?>> ret = JSON.parseObject(json, new TypeReference<TestA<List<TestB>>>() {
        }.getType());
        System.out.println("a class name: " + ret.getA().getClass().getName());
        System.out.println("b class name: " + ret.getA().get(0).getClass().getName());
    }

//    @Test
    public void testOk2() {
        System.out.println("---------------testOk2---------------");
        ParameterizedTypeImpl inner = new ParameterizedTypeImpl(new Type[]{TestB.class}, List.class, List.class);
        ParameterizedTypeImpl outer = new ParameterizedTypeImpl(new Type[]{inner}, TestA.class, TestA.class);
        TestA<List<?>> ret = JSONObject.parseObject(json, outer);
        System.out.println("a class name: " + ret.getA().getClass().getName());
        System.out.println("b class name: " + ret.getA().get(0).getClass().getName());
    }
}
4

提供下测试用例?

您好,现在有在修复吗?

5

提供下测试用例?

您好,现在有在修复吗?

@darren-wang soory, 最近工作太忙了, 你有兴趣直接发个 pr 吧