Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] 空List反序列化后向其中添加元素时报java.lang.UnsupportedOperationException异常 #1835

Closed
dcrpp opened this issue Sep 5, 2023 · 15 comments
Assignees
Labels
bug Something isn't working fixed
Milestone

Comments

@dcrpp
Copy link

dcrpp commented Sep 5, 2023

问题描述

ObjectReaderImplList中239行使用了Collections.emptyList(),导致空List反序列化后向其中添加元素时报java.lang.UnsupportedOperationException异常

环境信息

  • 版本信息:[Fastjson2 2.0.40]

重现步骤

参见以下bug复现代码

public class App {

    public static void main(String[] args) {
        List<Person> sourceList = new ArrayList<>();
//        sourceList.add(new Person("zhangsan"));
        Map<String, Object> source = new HashMap<>();
        source.put("data", sourceList);

        String json = JSON.toJSONString(source);

        System.out.println(json);

        JSONObject target = JSON.parseObject(json);
        Type type = getType();
        List<Person> targetList = target.getObject("data", type);

        System.out.println(targetList.getClass().getName());

        targetList.add(new Person("lisi"));

        System.out.println(JSON.toJSONString(targetList));
    }

    public static Type getType() {
        try {
            App app = new App();
            Method method = app.getClass().getMethod("test");
            return method.getGenericReturnType();
        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    public List<Person> test() {
        return new ArrayList<>();
    }

    public static class Person {

        private String name;

        public Person() {

        }

        public Person(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

    }

}

期待的正确结果

空List反序列化后可以正常向其中添加元素。

全面排查并仔细评估所有使用了Collections.emptyList()和Collections.emptySet()的地方,有类似问题的地方建议一并修改。

相关日志输出

以上bug复现代码日志输出如下

{"data":[]}
java.util.Collections$EmptyList
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractList.add(AbstractList.java:153)
	at java.base/java.util.AbstractList.add(AbstractList.java:111)
	at com.dcr.demo.App.main(App.java:34)

附加信息

我使用fastjson2做Openfeign接口的序列化及反序列化,当Openfeign接口方法返回值不为空时,向list中添加元素不报异常。当返回空List后,再向list中添加元素时会报UnsupportedOperationException异常。

@dcrpp dcrpp added the bug Something isn't working label Sep 5, 2023
@yanxutao89 yanxutao89 self-assigned this Sep 6, 2023
yanxutao89 added a commit to yanxutao89/fastjson2 that referenced this issue Sep 6, 2023
@wenshao wenshao added this to the 2.0.41 milestone Sep 9, 2023
@wenshao wenshao added the fixed label Sep 9, 2023
@wenshao
Copy link
Member

wenshao commented Sep 9, 2023

@wenshao
Copy link
Member

wenshao commented Oct 6, 2023

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

@wenshao wenshao closed this as completed Oct 6, 2023
@iwangjie
Copy link

iwangjie commented Jan 5, 2024

@wenshao 此问题在 2.0.42以及以后版本又出现了

@cycle2zhou
Copy link

从42-45版本都有这个问题

@wenshao wenshao removed the fixed label Jan 27, 2024
@wenshao wenshao modified the milestones: 2.0.41, 2.0.46 Jan 27, 2024
@wenshao
Copy link
Member

wenshao commented Jan 27, 2024

@cycle2zhou 新版本的异常信息能提供下么?

@bert82503
Copy link

bert82503 commented Jan 29, 2024

@cycle2zhou 新版本的异常信息能提供下么?

温少,这是我们升级dubbo-3.2.10后遇到的异常调用栈。 @wenshao

{"timestamp":"2024-01-26T14:02:04.917",
"message":" [DUBBO] Decode rpc result failed: org.apache.dubbo.common.serialize.SerializationException: java.lang.UnsupportedOperationException, dubbo version: 3.2.10, current host: 192.168.108.128, error code: 4-20. This may be caused by , go to https://dubbo.apache.org/faq/4/20 to find instructions. ","thread":"DubboServerHandler-192.168.108.128:8504-thread-392","logger":"org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult","level":"WARN","trace_id":"21c6f0fc84474d55a2e32f6cd7ddbc13.70.17062485467755809","exception":""}

java.io.IOException: org.apache.dubbo.common.serialize.SerializationException: java.lang.UnsupportedOperationException
  at org.apache.dubbo.common.serialize.DefaultSerializationExceptionWrapper.handleToIOException(DefaultSerializationExceptionWrapper.java:353)
  at org.apache.dubbo.common.serialize.DefaultSerializationExceptionWrapper.access$000(DefaultSerializationExceptionWrapper.java:27)
  at org.apache.dubbo.common.serialize.DefaultSerializationExceptionWrapper$ProxyObjectInput.readObject(DefaultSerializationExceptionWrapper.java:172)
  at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.handleValue(DecodeableRpcResult.java:176)
  at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:110)
  at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:153)
  at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:61)
  at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:49)
  at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:64)
  at org.apache.dubbo.common.threadpool.ThreadlessExecutor$RunnableWrapper.run(ThreadlessExecutor.java:151)
  at org.apache.dubbo.common.threadpool.ThreadlessExecutor.waitAndDrain(ThreadlessExecutor.java:77)
  at org.apache.dubbo.rpc.AsyncRpcResult.get(AsyncRpcResult.java:219)
  at org.apache.dubbo.rpc.protocol.AbstractInvoker.waitForResultIfSync(AbstractInvoker.java:292)
  at org.apache.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:194)
  at org.apache.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:71)
  at io.seata.integration.dubbo.ApacheDubboTransactionPropagationFilter.invoke(ApacheDubboTransactionPropagationFilter.java:69)
  at com.leoao.starter.dubbo.generic.GenericFilterWrapper.invoke(GenericFilterWrapper.java:67)
  at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:349)
  at com.alibaba.dubbo.rpc.Invoker$CompatibleInvoker.invoke(Invoker.java:77)
  at io.seata.integration.dubbo.alibaba.AlibabaDubboTransactionPropagationFilter.invoke(AlibabaDubboTransactionPropagationFilter.java:45)
  at com.alibaba.dubbo.rpc.Filter.invoke(Filter.java:34)
  at com.leoao.starter.dubbo.generic.GenericFilterWrapper.invoke(GenericFilterWrapper.java:67)
  at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:349)
  at org.apache.dubbo.rpc.filter.RpcExceptionFilter.invoke(RpcExceptionFilter.java:40)
  at com.leoao.starter.dubbo.generic.GenericFilterWrapper.invoke(GenericFilterWrapper.java:67)
  at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:349)
  at org.apache.dubbo.rpc.filter.ActiveLimitFilter.invoke(ActiveLimitFilter.java:85)
  at com.leoao.starter.dubbo.generic.GenericFilterWrapper.invoke(GenericFilterWrapper.java:67)
  at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:349)
  at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CallbackRegistrationInvoker.invoke(FilterChainBuilder.java:197)
  at org.apache.dubbo.rpc.protocol.ReferenceCountInvokerWrapper.invoke(ReferenceCountInvokerWrapper.java:106)
  at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invokeWithContext(AbstractClusterInvoker.java:412)
  at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:82)
  at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:366)
  at org.apache.dubbo.rpc.cluster.router.RouterSnapshotFilter.invoke(RouterSnapshotFilter.java:46)
  at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:349)
  at ...
Caused by: org.apache.dubbo.common.serialize.SerializationException: java.lang.UnsupportedOperationException
  ... 150 common frames omitted
Caused by: java.lang.UnsupportedOperationException: null
  at java.util.AbstractList.add(AbstractList.java:148)
  at java.util.AbstractList.add(AbstractList.java:108)
  at com.alibaba.fastjson2.reader.ORG_20_5_Page.readJSONBObject(Unknown Source)
  at com.alibaba.fastjson2.JSONB.parseObject(JSONB.java:540)
  at org.apache.dubbo.common.serialize.fastjson2.FastJson2ObjectInput.readObject(FastJson2ObjectInput.java:171)
  at org.apache.dubbo.common.serialize.DefaultSerializationExceptionWrapper$ProxyObjectInput.readObject(DefaultSerializationExceptionWrapper.java:170)
  ... 147 common frames omitted

@wenshao
Copy link
Member

wenshao commented Jan 29, 2024

Page的类结构可以提供下么?

@bert82503
Copy link

bert82503 commented Jan 29, 2024

Page的类结构可以提供下么?

温少,这个问题调整后已修复 @wenshao

先前使用private List<T> data = Collections.emptyList();,这个问题调整后已修复。

package basic.model;

import lombok.Data;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 分页模型
 */
@Data
public class Page<T> implements Serializable {
    private static final long serialVersionUID = 201612008194101999L;

    /**
     * 页面元素
     */
    // 有问题
    private List<T> data = Collections.emptyList();
    // 修复正常
//    private List<T> data = new ArrayList<>();

    /**
     * 当前页码
     */
    private Integer pageIndex = 0;

    /**
     * 每页展示数
     */
    private Integer pageSize = 0;

    /**
     * 总页码
     */
    private Integer totalPage = 0;

    /**
     * 总数
     */
    private Long total = 0L;

    /**
     * 构建
     */
    public static <T> Page<T> of (List<T> data, Integer pageIndex, Integer pageSize, Long total) {
        Page<T> page = new Page<>();
        page.setData(data);
        page.setPageIndex(pageIndex);
        page.setPageSize(pageSize);
        page.setTotal(total);
        page.setTotalPage(total, pageSize);
        return page;
    }

    @Deprecated
    public void setData(List<T> items) {
        if (CollectionUtils.isEmpty(items)) {
            return;
        }
        this.data = items.stream().filter(Objects::nonNull).collect(Collectors.toList());
    }

    /**
     * 设置总页数
     *
     * @param total 总数
     * @param pageSize 分页大小
     */
    @Deprecated
    public void setTotalPage (Long total, Integer pageSize) {
        this.total = total;
        this.pageSize = pageSize;
        if (total == null || total == 0) {
            return;
        }

        if (pageSize == null || pageSize == 0) {
            return;
        }

        int totalPage;
        if (0 == total % pageSize)
            totalPage = (int) (total / pageSize);
        else
            totalPage = (int) (total / pageSize + 1);
        if(0==totalPage)
            totalPage = 1;
        this.totalPage = totalPage;
    }
}

@wenshao wenshao reopened this Jan 29, 2024
@wenshao
Copy link
Member

wenshao commented Jan 29, 2024

问题无法重现,能够调试看下序列化之前这个Page里面的data是什么类型么?或者调试反序列化报错UnsupportedOperationException时当前List的类型

wenshao added a commit that referenced this issue Jan 29, 2024
@wenshao wenshao modified the milestones: 2.0.46, 2.0.47 Jan 29, 2024
@bert82503
Copy link

bert82503 commented Jan 31, 2024

问题无法重现,能够调试看下序列化之前这个Page里面的data是什么类型么?或者调试反序列化报错UnsupportedOperationException时当前List的类型

温少,data是java.util.Collections.EmptyList类型 @wenshao

package basic.model;

import lombok.Data;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 分页模型
 */
@Data
public class Page<T> implements Serializable {
    private static final long serialVersionUID = 201612008194101999L;

    /**
     * 页面元素
     */
    // 有问题
    private List<T> data = Collections.emptyList();
    // 修复正常
//    private List<T> data = new ArrayList<>();

    /**
     * 当前页码
     */
    private Integer pageIndex = 0;

    /**
     * 每页展示数
     */
    private Integer pageSize = 0;

    /**
     * 总页码
     */
    private Integer totalPage = 0;

    /**
     * 总数
     */
    private Long total = 0L;

    /**
     * 构建
     */
    public static <T> Page<T> of (List<T> data, Integer pageIndex, Integer pageSize, Long total) {
        Page<T> page = new Page<>();
        page.setData(data);
        page.setPageIndex(pageIndex);
        page.setPageSize(pageSize);
        page.setTotal(total);
        page.setTotalPage(total, pageSize);
        return page;
    }

    @Deprecated
    public void setData(List<T> items) {
        if (CollectionUtils.isEmpty(items)) {
            return;
        }
        this.data = items.stream().filter(Objects::nonNull).collect(Collectors.toList());
    }

    /**
     * 设置总页数
     *
     * @param total 总数
     * @param pageSize 分页大小
     */
    @Deprecated
    public void setTotalPage (Long total, Integer pageSize) {
        this.total = total;
        this.pageSize = pageSize;
        if (total == null || total == 0) {
            return;
        }

        if (pageSize == null || pageSize == 0) {
            return;
        }

        int totalPage;
        if (0 == total % pageSize)
            totalPage = (int) (total / pageSize);
        else
            totalPage = (int) (total / pageSize + 1);
        if(0==totalPage)
            totalPage = 1;
        this.totalPage = totalPage;
    }
}

Page类只有JDK生成的默认构造函数,其data字段值会初始化为Collections.emptyList()
提供者端是通过静态方法Page.of(...)构造Page对象,会返回ArrayList<>()的多条数据;
消费者端,FastJson2通过默认构造函数创建Page对象,data字段只能使用默认值Collections.emptyList(),其实现类是jdk-8的EmptyList<E> extends AbstractList<E>,EmptyList未覆盖add方法,AbstractList定义了add(int index, E element)

image image

@wenshao
Copy link
Member

wenshao commented Feb 23, 2024

这个不好解决呢

@wenshao wenshao modified the milestones: 2.0.47, 2.0.48 Feb 23, 2024
@bert82503
Copy link

这个不好解决呢

温少,这个我们通过初始化其他类型,升级解决了。是我们使用的问题,不是框架层面的。不用解决哈

@wenshao wenshao closed this as completed Mar 25, 2024
@wenshao
Copy link
Member

wenshao commented Mar 25, 2024

https://github.com/alibaba/fastjson2/releases/tag/2.0.48
请用新版本

@Alleninggx
Copy link

Alleninggx commented Jun 22, 2024

@wenshao 使用了2.0.49,此问题还是会出现,会考虑优化这个问题吗?

wenshao pushed a commit that referenced this issue Jun 25, 2024
* fix unsupported operation error when field is Collections.EMPTY_LIST, for issue #2691 and issue #1835
@wenshao wenshao added the fixed label Jun 25, 2024
@wenshao wenshao modified the milestones: 2.0.48, 2.0.52 Jun 25, 2024
@wenshao
Copy link
Member

wenshao commented Jul 14, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fixed
Projects
None yet
Development

No branches or pull requests

7 participants