码迷,mamicode.com
首页 > 其他好文 > 详细

mybatis 3.3.0之前版本的一个空指针BUG

时间:2021-04-15 12:39:39      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:ted   计算   布尔值   递归   ola   对象   key   中断   stand   

  最近在排查一个ORM层相关的生产BUG时,在网上查找资料,看到一些人反馈mybatis 3.3.0之前版本的一个空指针BUG,于是自己看源码研究了一下,还真有这种问题,下面以mybatis 3.1.1版本源码作为说明。
  BUG产生的源头在OGNL表达式上,直接根据结论放上自己的一个测试类,断点选择在OgnlCache.getValue()方法执行的这一行:
技术图片

  OgnlCache.getValue()方法的源码比较简单,思路和我们使用其他表达式框架(如google的aviator)类似,即为已经编译好的表达式构建一个缓存并尝试从该缓存中获取编译后表达式,如果未能获取到则进行编译并存入缓存:

技术图片

  可以看到,表达式被编译成一个Node对象,查看一下接口Node的源码,发现其具有parent、child相关方法,因此可以大胆猜测Node应该是一个树状结构:

技术图片

  使用IDEA自带的功能,我们简单查看一下Node的子类,种类还是挺丰富的,对应了mybatis标签中各式各样的表达式语法:

技术图片

  接下来开始debug,一路前进来到Ognl类的getValue方法,如下图所示:

技术图片

  Debug控制台中的信息证实了我们之前关于Node的猜想:“表达式编译后的Node为一棵树”,展开这颗tree,详细看看树的结构:

技术图片

  由此,我们推测表达式的计算顺序应该是沿着叶子节点向其父节点的顺序逐层递归求值,最终在根节点求得整颗树的值。进入上面图片中断点停留的“Object result = ((Node)tree).getValue(ognlContext, root)”方法,调用栈跳转到SimpleNode的getValue方法,并最终进入同一个类的evaluateGetValueBody方法:

技术图片

  到这里就能明显看出问题了,这段神奇的代码在没有锁等同步措施保护的情况下,直接修改非并发安全的共享成员变量constantValueCalculated、hasConstantValue、constantValue的值,明显的竞态条件,但根据这个并不足以得到产生空指针异常的原因,我们继续向前debug:

技术图片

  根据控制台信息,当前执行的表达式是ASTAnd,由于它并不是一个常量,因此this.isConstant(context)返回false并赋值给成员变量hasConstantValue(它的默认值也是false),可以看到,无论constantValueCalculated值为true还是false,非常量表达式最终都会通过上图中的高亮行来求得表达式的值,所以对于非常量表达式,这段代码其实没有并发安全问题;但反之,对于常量表达式,这段代码有并发安全问题,因为hasConstantValue会被更新为true,那么在并发的情况下,会出现一个线程通过执行上图中131行代码求得常量值,另外一个线程只“看到”了constantValueCalculated和hasConstantValue的最新值true而没有“看到”constantValue的最新值,导致执行135行代码返回null。

  为了确认空指针异常是由常量表达式执行evaluateGetValueBody方法时的并发安全导致,我们继续向前debug,进入到ASTAnd的getValueBody方法实现中:
技术图片

  可以看到,这段代码逻辑也证实了之前关于表达式树的值计算顺序猜想。在继续debug之前,我们需要思考一下空指针可能出现的位置,回顾一下前面图片中控制台打印的ASTAnd这颗表达式树的结构(为了简单,直接将同一张图片复制在下面):ASTConst这个最有可能为常量表达式的节点出现在叶子节点上,我们假设它为常量表达式,在并发的情况下有可能向ASTProperty表达式返回null值,也就是说,ASTProperty表达式会使用一个null值作为输入条件执行某种计算逻辑,那么空指针异常是否出现在这次计算中呢?

技术图片

  为了验证我们的猜想,向前debug到ASTProperty的表达式计算逻辑处,可以看到此表达式获取到一个property对象后,通过OgnlRuntime.getProperty方法继续获取值,这个行为是不是有点像Map的get()方法?如果是这样,那么this.getProperty方法应该就是调用执行ASTConst表达式来“获取map的key”了。

技术图片

  查看ASTProperty的getProperty方法,果然是调用子表达式(即ASTConst)进行计算,来获取一个key:

技术图片

  至于ASTConst是否为常量表达式,其实很简单,它覆写了父类SimpleNode的isNodeConstant方法,并返回一个布尔值true。而SimpleNode中的isConstant方法就是直接调用并返回isNodeConstant方法的值。因此,ASTConst为一个常量表达式。

  回过头来我们继续验证ASTProperty的表达式计算逻辑是否会产生空指针异常,为此我们根据当前的debug信息,专门写了一段代码测试OgnlRuntime.getProperty方法在传入值为null的property时,是否会产生空指针异常,结果不负众望:
技术图片

  总结:当前版本mybatis的ognl引擎在并发执行ASTConst表达式计算时,由于未进行同步控制,导致可能向上返回null值,该null值被ASTProperty作为key从数据对象中进行取值,引发空指针异常。而在mybatis 3.3.0版本中,通过提升内嵌的OGNL引擎版本修复了此问题,修复后SimpleNode的evaluateGetValueBody方法源码如下,通过一个方法内的临时变量constant以及为_hasConstantValue添加volatile关键字保证了不同线程执行该方法时,136行返回的值要么是最新的_constantValue(通过130行代码计算得到,且通过volatile刷新对其他线程可见性),要么是通过136行中执行getValueBody方法获得的。

技术图片

 技术图片

 

  参考:关于OGNL表达式的这个BUG可以在链接“https://issues.apache.org/jira/browse/OGNL-121”中找到。

 
 
 
 
 

mybatis 3.3.0之前版本的一个空指针BUG

标签:ted   计算   布尔值   递归   ola   对象   key   中断   stand   

原文地址:https://www.cnblogs.com/manayi/p/14660413.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!