几点说明:
- 我所在的项目密级很高,所以我需要项目相关的信息打码,抱歉更无法提供项目源码(更新,已经提供了一份脱敏的源码)
- 我会尽量用描述的方式描述清楚问题,并给出自己的分析过程
环境:
java 8
fastjson 1.2.61(1.2.58也试过)
背景:
我们有一个类SkDto.java,这个类的字段比较多,且含有一些内部static类。而且我们用lombok标注它。
我们用这种方式跑单测:
str = "{}"; // 原始值不是{},此处简化下,因为{}也能复现
SkDto skDto= JSON.parseObject(str, SkDto.class);
在一次升级之后,我们增加了几个字段之后,这一行代码无法通过单测。
报错是:
testcase XXXXXXXXXXXXXXX > testDTO: java.lang.VerifyError: (class: com/alibaba/fastjson/parser/deserializer/FastjsonASMDeserializer_4_SkDTO, method: deserialze signature: (Lcom/alibaba/fastjson/parser/DefaultJSONParser;Ljava/lang/reflect/Type;Ljava/lang/Object;I)Ljava/lang/Object;) Illegal target of jump or branch
我们给SkDTO.java删字段,发现删除long类型的一个成员变量之后,仍然无法工作。但是删除一个Arraylist类型的成员变量 或者随便多删除几个成员变量之后,即可工作。所以猜测我们是超出了某个大小限制,只有把类缩减到这个限制以下之后,才能工作。
这个限制不是简单的数量限制,因为删除一个long类型的成员变量 和删除一个Arraylist类型的成员变量 的结果不同。
开始验证猜测:
分别打开两个IDE,一个IDE用有问题的SkDTO.java,一个IDE用“删除一个Arraylist类型的成员变量 ”之后的SkDTO.java。
JSON.java 560行:return parseObject(text, clazz, new Feature[0]);
调用了
JSON.java 383行:T value = (T) parser.parseObject(clazz, null);
调用了
DefaultJSONParser.java 673行: ObjectDeserializer deserializer = config.getDeserializer(type);
调用了
ParseConfig.java 411行:return getDeserializer((Class<?>) type, type);
调用了
ParseConfig.java 678行:deserializer = createJavaBeanDeserializer(clazz, type);
调用了
ParseConfig.java 840行:return asmFactory.createJavaBeanDeserializer(this, beanInfo);
调用了
ASMDeserializerFactory.java 89-90行:
Class deserClass = classLoader.defineClassPublic(classNameFull, code, 0, code.length);
Constructor constructor = deserClass.getConstructor(ParserConfig.class, JavaBeanInfo.class);
在此之前,两个IDE的debug都看不出来明显区别,在这里出现了完全不同的反应:
其中一个IDE在90行抛出了异常。
分别在两个IDE的第90行打断点:
比较两个class,发现两个ASM类有明显不同,一个有构造函数,一个没有:

看构造的过程,都是ClassLoader的,唯一区别就是传入的参数:

本行之上都是描述事实,本行之下是猜想内容:
- 当类大小比较大的时候,或者触发了某个条件,ASM生成的字节码不正常。
- 当一个类的大小超过64K时候,用protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)生成的类和ASMSerializerFactory配合的不好;
我查了不少资料,我和这位朋友的状态非常相似:
#1207
和这个帖子里面的评论的朋友也有一些相似,但是不完全相同:
#1092
我的症状是只能减字段才能解决,不能加字段;他的症状是增减字段都能解决
几点说明:
环境:
java 8
fastjson 1.2.61(1.2.58也试过)
背景:
我们有一个类SkDto.java,这个类的字段比较多,且含有一些内部static类。而且我们用lombok标注它。
我们用这种方式跑单测:
str = "{}"; // 原始值不是{},此处简化下,因为{}也能复现
SkDto skDto= JSON.parseObject(str, SkDto.class);
在一次升级之后,我们增加了几个字段之后,这一行代码无法通过单测。
报错是:
testcase XXXXXXXXXXXXXXX > testDTO: java.lang.VerifyError: (class: com/alibaba/fastjson/parser/deserializer/FastjsonASMDeserializer_4_SkDTO, method: deserialze signature: (Lcom/alibaba/fastjson/parser/DefaultJSONParser;Ljava/lang/reflect/Type;Ljava/lang/Object;I)Ljava/lang/Object;) Illegal target of jump or branch
我们给SkDTO.java删字段,发现删除long类型的一个成员变量之后,仍然无法工作。但是删除一个Arraylist类型的成员变量 或者随便多删除几个成员变量之后,即可工作。所以猜测我们是超出了某个大小限制,只有把类缩减到这个限制以下之后,才能工作。
这个限制不是简单的数量限制,因为删除一个long类型的成员变量 和删除一个Arraylist类型的成员变量 的结果不同。
开始验证猜测:
分别打开两个IDE,一个IDE用有问题的SkDTO.java,一个IDE用“删除一个Arraylist类型的成员变量 ”之后的SkDTO.java。
JSON.java 560行:return parseObject(text, clazz, new Feature[0]);
调用了
JSON.java 383行:T value = (T) parser.parseObject(clazz, null);
调用了
DefaultJSONParser.java 673行: ObjectDeserializer deserializer = config.getDeserializer(type);
调用了
ParseConfig.java 411行:return getDeserializer((Class<?>) type, type);
调用了
ParseConfig.java 678行:deserializer = createJavaBeanDeserializer(clazz, type);
调用了
ParseConfig.java 840行:return asmFactory.createJavaBeanDeserializer(this, beanInfo);
调用了
ASMDeserializerFactory.java 89-90行:
Class deserClass = classLoader.defineClassPublic(classNameFull, code, 0, code.length); Constructor constructor = deserClass.getConstructor(ParserConfig.class, JavaBeanInfo.class);
在此之前,两个IDE的debug都看不出来明显区别,在这里出现了完全不同的反应:
其中一个IDE在90行抛出了异常。
分别在两个IDE的第90行打断点:


比较两个class,发现两个ASM类有明显不同,一个有构造函数,一个没有:
看构造的过程,都是ClassLoader的,唯一区别就是传入的参数:
本行之上都是描述事实,本行之下是猜想内容:
我查了不少资料,我和这位朋友的状态非常相似:
#1207
和这个帖子里面的评论的朋友也有一些相似,但是不完全相同:
#1092
我的症状是只能减字段才能解决,不能加字段;他的症状是增减字段都能解决