This note was originally written in Chinese. The translation is on the way and will come soon, before which you can use the google translation plugin to help you to understand.

DBug Repair Records for Defects4J

Lang-1 (regression error:1)

specification: (1)整数前面的0是没有意义的;(2)带符号位的整数类型需要考虑符号位

bug 说明:对于NumberUtils中的createNumber(String)函数,当输入的字符串的长度刚好是long或integer的极限长度时,由于缺乏考虑最高位为1(符号位,即负数)的情况导致出错。

修复过程描述:(代码,用时30mins,涉及单个函数)

(1)错误定位:注释掉不相关的错误测试,跑测试,定位到crash的位置。

(2)观察出错的测试输入数据是否具有特殊性,比如此处出错时的输入数据是字符串形式表示的数值量,根据函数的功能(根据函数名称以及测试获得)知道其将输入的字符串转换为数值对象,其目标是尽可能用小的数值类型进行表示。比如能用integer进行表示的,不用long类型。根据经验我们知道当将字符串转换为数值时需要考虑其表示的最大位数正负性。而根据eclipse输出的出错信息得知其刚好是涉及到正负相关,而程序中对临界值没有特殊考虑,修复方案添加临界边值的判断条件:(hexDigits == 16 && str.charAt(pfxLen) > '7')(hexDigits == 8 && str.charAt(pfxLen) > '7')。但是测试发现还是存在错误,原因在于测试输入的数据是0x00f000000时,生成了long类型数据,期望的是integer数据,观察知道数据,对于数据的最高位需要进行0过滤,因此添加了下面代码的最上面的while循环,最终测试通过。

标准patch:

 

Lang-2

specification:(1)字符串中的“#”是特殊字符;(2)函数的输入字符串中含有该字符时期望抛出IllegalArgumentException

bug说明:针对java 7,对Locale的variant没有验证处理,针对ja_JP_JP_#u-ca-japaneseth_TH_TH_#u-nu-thai,根据测试要求应该输出异常

修复说明:(代码+注释(辅助理解程序),用时1.5h,涉及单个函数)

首先跑测试找到失败的测试用例ja_JP_JP_#u-ca-japaneseth_TH_TH_#u-nu-thai,然后单步跟踪执行发现能够正常执行,没有发生异常(目标是产生异常)。因此在测试用例中寻找相似的测试用例进行测试对比,发现相似的测试输入有sr_ME_#Latnsr_BA_#Latnsr__#Latn以及sr_RS_#Latn,并且都能够正常通过测试。同样使用单步调试,发现当输入的Locale为上面4种时,测试函数并没有调用toLocale(String)函数,而未通过的两个测试输入都调用了该函数,且产生了不是期望的结果。因为没有执行出错的函数,因此没有产生修复帮助,无可比性。然后对比其他的测试输入,发现通过的测试用例调用该函数时传入的参数中都没有"#"字符以及之后的字符,因此生成了如下的修复方案:

运行测试,结果正确。但是感觉修复得很trivial,并没有搞懂真正的原因是什么,后来查看修复的patch,结果就是这样修复的。再上网查,原因是java 1.7对Localevariant做了严格的验证导致的,具体怎么做的不是很清楚,但是通过上面的修复可以解决这个问题,但是应该还是算是一个取巧的方法。

标准patch:

 

Lang-3

specification:(1)单精度浮点数和双精度浮点数的精度非别是7位和16位,该函数存在精度损失

bug说明:将字符串转换为为数字,Float和Double类型由于精度不够导致转换之后的数字截断

修复过程描述:(代码,用时20min,涉及单个函数)

根据测试(assert信息)很容易发现字符串转换为数字导致精度不够,定位到createNumber(String)函数的最后几行,由代码知道,其策略是从精度由低到高尝试创建数据类型(Float->Double->BigDecimal),然后判断当前创建的数据是否满足精度要求决定是否提高精度,通过打印输入的字符串和生成的数据对比发现,对于创建的数据的精度的判断不够严格,导致创建的数据已经产生截断而没有被发现。通过通读该函数代码发现,之前已经解析出字符串所代表数字的整数部分,小数部分和指数部分,以及对应的长度。因此,直接通过对应数据类型的精度位数和小数部分的长度进行对比即可,为了修改最小,直接在原始条件的基础之上添加小数位数的判断,修复代码如下:

标准patch:

 

Lang-4

specification:(1)字面相同的两个CharSequence类型变量,其hash值是不同的(2)Map表的查询依赖的是Hash值。(3)函数的查询过程直接使用了CharSequence类型进行Map表的查询导致返回为空

bug说明:在Map表中查询元素CharSequence类型数据返回一直为null,原因很明显是查询操作get(key)在比较key值的时候依据hash值进行比较的

修复过程描述:(代码,用时10mins,涉及单个函数)

阅读源代码我们知道函数的功能是从HashMap中查询数据,出错的原因是内容相同的两个CharSequence实例在应用到HashMap的查询上时,由于其查询依据的是keyHashCode信息,导致查询结果为空,因此很容易想到的修复方案是将比较的两个实例转换为可以进行内容对比的同等实例。最容易想到的就是String类,故修复方案如下:(改动最小方案,只涉及到一个函数。其他修改方法涉及其他函数,比如存入HashMap中的类型改成String)

标准patch:

 

Lang-5

specification:(1)对Locale的解析过程通常情况下输入的是xx_XX_yyy格式(2)根据测试输入的是_XX_yyy格式时应该正确解析(3)观察发现xxXX是相同的内容,只是区分了大小写。

bug说明:对于Locale的解析,当语言为空只有国家时,如_GB,要求能正常解析

修复过程描述:(源代码,用时20mins,涉及单个函数)

运行测试程序发现,当测试的输入是以_开头时,原来的代码不能进行解析,会抛出异常,因为原始的代码根据要求前两个字母是语言标志,一定是小写字母,但是没有考虑不包含语言标志的情况,即直接以_开头。所以需要对输入的字符串的首字母进行判断,再结合测试中所有以_开头的测试输入的期待输出结果,可以得出结论:对于以_开头的字符串,可以跳过前两个字符的比较,后面的字符的判断和可以套用输入存在语言标志的情况。因此分类处理如下:情况一,非_开头的要求一定是以小写字母开头,即保持原来的不变;情况二,当以_开头时,类似于在原来代码从第三个字符之后的判断。因为代码中用的是常数数字对每一位进行判断,而且输入的字符串是final类型,因此需要添加一个完整的分支,将所有对应的每一位的判断填充上,因此修复方案如下:

标准patch:

 

Lang-6

specification:(1)循环变量在循环的过程中没有使用(2)循环变量和循环体中的一个变量非常相似(3)该变量是对字符串进行下标访问(4)出现越界访问Exception(5)循环变量受字符串的长度约束

bug说明:访问字符串中的字符时越界

修复过程描述:(源代码,用时10mins,涉及单个函数)

通过测试定位到出错的代码行,然后根据错误信息发现是字符串的越界访问错误,定位到了一处字符计数代码。只是根据代码的命名大体猜一下代码的功能,并没有完全理解。但是在观察代码的时候发现:在循环的语句中并没有用到循环计数器,而这个循环计数器根据之前的代码应该是和字符串的长度相关,同时与循环体里用到的变量名称非常相似,极易混淆,所以尝试修改函数体里的字符串的下标变量:将pos修改为pt,重新跑测试,成功通过。

标准patch:

 

Lang-7

specification:(1)测试输入的字符串以- -开头时期望抛出NumberFormatException(2)程序实际返回了null

bug说明:在将字符串转换为数值时,以- -开头的字符串应该抛出异常,但是程序返回了null

修复过程描述:(源代码,用时5分钟,涉及单个函数)

运行测试直接定位到错误代码行,根据错误信息:当测试代码的输入是- - 1.1E-700F时应该抛出异常,但是查看代码发现,对于以- -开头的字符串直接返回了null,不符合测试的要求。因此直接改成期待的输出即可。修改如下:

标准patch:

 

Lang-8

specification:(1)测试生成时间的时区与期望的时区不符(2)传入的参数包含时区参数,并与期望的结果相同(3)传入的时区参数在函数中没有用,而是用了全局的时区参数

bug说明:在生成时间时所用的时区不是传入参数指定的时区

修复过程描述:(源代码,用时10分钟,涉及一个函数)

单步调试测试用例,定位到解析时间函数中。在调试的过程中发现,期待的时区和传入的calendar变量的时区是一致的,而最后生成的时间所用的时区是不一致的,因此确定是时区的解析有问题。在定位到时区解析函数后,发现传入的calendar变量没有使用,因此确定错误是应该将里边的默认TimeZone修改成传入的参数。修复方案如下,成功通过所有测试。

标准patch:

 

Lang-9

specification:(1)当输入参数是'd'd时正常,当输入参数是'd'd'时应该抛出异常(2)上网查找发现对字符串的正则解析过程中,字符串中的引号应该是偶数(3)在单步调试过程中发现对'd'd'的解析过程没有解析完全,当解析到最后一个d的时候就返回了

bug说明:不能识别出含有未配对引号的字符串

修复过程描述:(源代码+注释+上网搜索,用时1h,涉及单个函数)

首先是跑测试发现在应该出现异常时,程序正常运行。对比两组测试用例,发现唯一的不同是正则表达式的字符串不同。根据单步调试,找出了在直接调用系统的函数时,抛出异常的原因是最后的单个引号不能被解析('d'd')。而代码中解析的结果是没有出现异常。再根据注释的信息发现期待有unterminated quote异常,因此上网搜索与regex相关的该异常,在StackOverflow上发现有人说传入的字符串中的'"应该是偶数出现。然后比较正确的测试输入'd'd确定错误是被解析的字符串中引号的数量是奇数。因此寻找对字符串进行正则表达式解析的代码块,然后单步调试比较成功测试和失败测试在该解析过程中的变量的变化情况。发现成功测试应该是把完整的字符串解析,而在解析'd'd'字符串时,解析到最后一个d就结束了,直接退出,因此通过对字符串的解析完整度的判断是一个可行的补丁。修复方法如下,正确通过所有测试。

标准patch:

 

Lang-10

specification:(1)对字符串的解析过程,原字符串中只有一个空格,但是生成的正则表达式可以匹配中间有两个空格的字符串(2)单步调试发现正则表达式在匹配字符串的时候是采用的单个字符逐个匹配(3)期望的结果是匹配失败,抛出异常

bug说明:空格的匹配需要严格数量一致

修复过程说明:(源代码,用时1.5h,涉及单个函数)

运行测试程序,发现使用系统的函数对M E进行正则表达式解析之后,期望的结果是不能匹配3 Tue,而实际的输出是可以正常匹配的。因此判断出错原因是生成的正则表达式错误,但是尝试使用单步调试时发现系统生成的正则表达式的pattern是看不到的,因此不能直接比较其生成的pattern的区别。所以,使用单步调试观察系统函数的匹配过程,发现其匹配的时候是按位进行匹配的。根据上面的数据知道M E中间只有一个空格,而3 Tue中间有两个空格字符导致其不能匹配成功。反过来再观察出错程序的解析过程发现生成的pattern中对空格的匹配是\s*+,因此导致多个空格也可以正常匹配。找到了真正的原因,开始找M Epattern的功能代码,发现代码内将空格直接解析成了\s*+,修改方法是改成单个空格。修复方法如下,正确通过所有测试。

标准patch:

 


Math-1 (regression error:1)

specification:(1)在将小数转换为分数表示形式的时候,当计算的中间过程数据溢出时期望的结果是一个很接近小数的分数,调试发现应该是上一次迭代的结果,而不应该抛出异常(2)另一个测试同样是小数转换发现期望的结果是抛出异常(3)比较两个测试发现一个指定了精度(期望异常),一个没有指定精度(近似分数)(4)源代码在一个判断是否溢出的分支中抛出了异常

bug说明:将double数转换为分数表示,在double类型数比较小的时候可能会出现整数超出范围

修复过程描述:(源代码+注释,用时1h,涉及单个函数)

通过跑测试发现,当输入的测试数据是0.5000000001时,期待的数据是1/2,在实际运行过程中却抛出了异常,根据源代码,发现通过迭代方式存在两组变量相互迭代更新,p1,q1保存上一次迭代的结果,p2,q2保存用于下一次更新的结果,当抛出异常时,输出对应的数据发现p1,q1刚好满足要求,而p2,q2溢出,结合注释说明:“当生成的分数与实数已经很接近的时候返回上一次(p1,q1)迭代的结果”。因此修复方案如下:

然而,当修复之后,发现Fraction.java中的错误信息和BigFraction.java的类似,因此生成同样的修复。再次跑测试,但是当测试的输入为0.75000000001455192时又期望产生异常,按照上面的修复方案是不能生成异常的。由于两处bug相似,开始思考针对两种不同的输入但是都同样产生越界为什么期望的结果不一样?我开始观察两组测试的不同点,发现在没有生成异常的测试中没有指明epsilon,而在期待生成异常的测试中指明了该精度,因此我想到的是如果没有人为的指定精度就返回一个精度最高的结果,如果已经指明精度就一定要达到精度,或者达不到精度要求而溢出,结合上面的分析,重新修改代码,修复方案如下:

应用上面的修复,可以正常跑过所有的测试。

标准patch:

 

Math-2

specification:failed(1)关于算法,没有理解,导致不能生成修复的specification

bug说明:分布的一个采样,采样的结果不在期待的范围之内

修复过程描述:(代码+注释+上网,用时2h,涉及多个函数【因为不知道是哪个函数错误】)失败

根据测试首先定位错误的位置,但是由于测试时随机采样,是一个算法问题,因此首先是读懂算法实现,根据注释信息(一个wiki网页链接),关于一个取样的算法,但是没有找到具体的公式,需要依据函数公式进行逆推,而且最后测试的判断条件是一个范围,难以确定错误所在。估计需要懂该算法或者作者自己修复。(或者更长的修复时间)

标准patch:

 

Math-3

specification:(1)根据抛出异常的位置,定位到出错位置是求解线性组合的函数(2)当输入的参数长度均为1 的时候由于计算过程中对下标访问导致出错(3)长度为1时期望的结果是两个数的乘积

bug说明:计算线性组合时,当输入的数组长度为1时出现数组越界

修复过程描述:(代码,用时10mins,涉及单个函数)

根据出错的测试函数,以及exception信息定位到求解线性组合的函数中,通过读代码发现对输入的参数的长度没有进行判断就进行下标访问导致出错,采用最简单的修复方法:根据测试函数得知,当数组长度为1时,直接将两个数进行相乘即可。修复代码如下:

标准patch:

 

Math-4

specification:(1)根据抛出异常的位置发现在求解两条线的相交点时由于不存在交点返回null,(2)接下来对该变量进行解引用操作导致异常

bug说明:在求两个向量的交点时由于对求交的返回是否为null缺少判断进行直接引用导致空指针错误

修复过程描述:(源代码,用时20mins,涉及单个函数)

根据测试以及错误输出定位信息,找到exception的代码行。根据代码的命名和阅读代码理解代码的作用是求两条直线的交点,而测试的输入是两条没有相交点的直线(平行线),因此应该返回为null,单步调试发现在调用intersection()函数之后返回两条直线的交点为null时,并没有进行特殊处理,而是直接作为参数传递给了另一个函数导致空指针错误。修复很容易,添加一个返回值判断,修复如下:

标准patch:

 

Math-5

specification:(1)当传入的参数的实部和虚部均为0时期望的结果是INF(2)程序在该情况下返回了NaN

bug说明:求复数的倒数,当复数为0时,其倒数应该是INF,而程序中返回的是NaN

修复过程描述:(源代码,5mins,涉及单个函数)

根据测试,容易定位出错代码的位置。已有代码对这种特殊情况作了特殊处理,只是返回的值不是期望的值。修改成期望的值即可,修复如下。

标准patch:

 

Math-6

specification:(1) 出错的原因是一个全局变量的值等于0,而期望的值应该是大于0的(2)根据该变量的名称判断是一个循环的标志量(3)调试定位发现错误应该出在doTest函数(4)函数的功能是迭代处理数据(5)在迭代的过程中有一个临时的循环变量计数,和未赋值的变量属于同类型,且名称接近(5)如果找到了该处并成功修改,则会发现依然会出现异常,调试发现原因是边界值maxIteration初始值是0,整个计算过程中没有被重新赋值

bug说明:在测试优化时,当优化结束之后获取到的迭代次数始终为0,interations=0

修复过程描述:(代码+注释(帮助理解程序),用时3h,涉及多个函数)

根据测试获得很多未通过测试,发现未通过的测试主要集中在一个doTest函数,但是并不能确定出错具体位置,使用单步调试,跟踪运行结束之后还是一样出错,没有发现具体原因。根据测试函数,最后的变量iteration需要大于0,因此从逆向寻找问题的原因。首先查找iteration的更新函数,只有一个,从此处打印输出,再次运行测试,发现并没有调用该函数,因此确定错误原因是未调用函数,然后再次单步调试,尝试理解代码的大体流程,发现了一处进行迭代计算的代码,根据命名和执行的情况(iteration,循环),确定应该在该处进行iteration的更新,即调用更新函数。然后再次运行测试函数,结果出现了另一种错误,根据提示找到出错原因是程序中对maxIteration没有赋值(该值是iteration的上界,越界会触发异常),一直为0,导致iteration增长超过限定值而触发错误。然后开始逆向寻找maxIteration的更新位置,发现在初始化的时候对其进行赋值,还有一处是通过测试参数传入赋值。但是根据测试传入的参数情况maxIteration并没有被赋值,因此只能是在其创建的时候进行初始化赋值,找到对象的创建位置,发现创建时其值是默认0,将其改成最大的整数值(根据常识,最大的整数是边界值)。然后再运行测试,结果刚刚运行的一个测试函数正确通过。然后运行所有测试,发现还有很多错误,查看代码发现测试的运行流程基本一致,因此考虑存在不同的Optimizer类型,每个类型中都存在相同的问题。然后找到对应的重写函数发现果然都没有添加对应iteration的更新,反而是声明了一个局部变量iter。修改方法是将局部变量都修改成了全局变量iteration的更新,再次运行测试可以正常通过。一个示例修改如下:

标准patch:

 

Math-7 (regression error:1)

specification:(1)函数的功能是规划事件。当部分事件发生时,没有发生的事件的时间戳标记会落后当前的时间导致异常

bug说明:(1)对事件规划的函数,有一个时间标志量记录当前执行完事件之后的最新时间(2)每个事件都有一个保存当前事件上次执行结束之后的时间(3)当一部分事件执行,一部分事件没有执行的时候,没有执行的事件的时间标志会落后最新的时间导致出错

修复过程描述:(代码+注释,用时3.5h,涉及多个函数【需要确定多个函数中应该在哪里修复】)

运行未通过的测试函数,使用单元测试会打印出出错的stacktrace,根据提示可以找到出错的路径,但是因为是对求得的值进行判断,并不会crash,因此不能找到具体哪条语句出错,也就不能找到相关变量的情况。使用单步调试,发现事件规划时存在两个事件,当其中的一个事件被执行而另一个事件没有执行时,当前的事件规划器interpolator的时间会更新,同时被执行的事件的时间也会被更新,但是没有执行的事件的时间记录保持不变,因此时间会落后而导致出错。因此找与事件的时间更新相关的代码。找到了一个函数setStep(double)函数是对事件的时间进行更新,以及另一个函数reinitializeBegin()对时间也会更新。根据名称发现第二个函数似乎和初始化相关,因此首先考虑的是应该缺少setStep()函数的调用。再详细的阅读代码以及使用单步调试,发现在被执行的事件结束后退出之前有一处更新事件的时间语句,如下:

然而没有被更新时间的事件不被包含在occuringEvents集合中,因此考虑到是否是开发者错误的使用了变量,因此将里边的occuringEvents修改成了一个包含所有事件的集合eventsStates,然后运行测试,结果通过测试。然后运行其他的所有测试,结果又出现了新的测试错误。因此考虑此处的修改方法不正确。然后再观察注释信息,根据Accept a step, triggering events and step handlers注释内容,该函数是执行一步操作,并不一定涉及所有的事件的更新任务。因此往上一层调用该函数的位置查找原因。在调用的函数中发现,这样的语句:

根据变量的判断和注释信息,可以猜测,如果没有结束运行的话需要为下一轮的运行做准备(注释),同时语句interpolator.storeTime()是更新了当前的执行时间,因此猜测该处可能是更新信息的位置,而在该处并不能访问到上面的这些事件,只能通过其他间接的方法。注意到上面的更新时间信息还有一个函数,就是reinitializeBegain()。在执行事件之前,对是否需要初始化的一个标志变量进行了判断,如果需要初始化则调用该函数,因此只要标记一下该标志量就好了。找到了更新该标志量的接口函数,修改方案如下:

修改之后运行所有测试通过。

标准patch:

 

Math-8

specification:(1)根据抛出的异常,对数组变量的赋值时由于类型不兼容导致错误(2)函数是对List中的元素进行采样并保存到该数组中(3)数组的初始化类型是使用了List中的第一个元素的类型(4)放入List中元素的类型是不确定的,需要都兼容

bug说明:由于给数组赋值了不兼容的类型导致错误

修复过程描述:(代码+上网,用时10mins,涉及单个函数)

运行测试,输出错误是java.lang.ArrayStoreException,通过网上搜索,获悉由于给数组赋值了不兼容的类型数据导致的,然后我将代码中用到的数组的类型和值类型输出,发现确实不同。根据函数的名称以及程序代码可以知道该函数是一个采样函数,在给定的一个list中采样,而数组的类型是采用了list中第一个元素的类型。再次运行输出list表中的所有元素的类型发现其中的数据类型并不一致。而采样函数又不能确定选择的是哪些元素,因此为了兼容性,直接选择了Object类型作为数组的类型,就可以接受任何类型的数据了。修复如下,正确通过所有测试。

标准patch:

 

Math-9

specification:(1)求向量的反向向量时精度损失(2)通过数学公式化简发现复杂的计算过程目的就是改变向量的方向,其他数据没有变化

bug说明:求直线的反向直线精度损失

修复过程描述:(代码+注释,用时30mins,涉及单个函数)

根据测试输出很容易发现在求直线的反向直线的时候精度损失造成测试失败。原始的代码是根据现有直线的方向和zero向量相减求取逆向向量,在此过程中损失了精度,最直接的办法就是复制当前的直线然后改变其方向。但是原始程序中涉及到与zero向量的计算过程,其计算的结果会对原始的数据产生什么样的影响是未知的。因此通过数学化简的方式模拟其计算的过程(其中用了一些函数是通过注释信息了解其功能),发现计算之后zero的值是保持不变的。因此很容易修改成下面的代码,正确通过所有测试。

标准patch:

 

Math-10

specification:(1)根据未通过的测试函数,当计算反正切函数时,传入的两个参数都是0时,不同的符号组合期望不同的结果

bug说明:对于反正切函数Atan2(double,double)输入的参数是正负0时不能输出正确结果。

修复过程描述:(源代码+上网,用时2h,涉及多个函数)

运行测试发现当对Atan2(double,double)函数做特殊的输入(+0,-0)进行测试时未输出正确的结果。由于不是crash错误,因此只能单步跟踪调试,找到当输入的参数是0时的特殊处理办法。但是调试过程中并没有发现对0的特殊判断,调试了很长时间都没有确定能修改的位置。最后考虑到输入是0Integer.MAX_INT等时,通常我们在写程序的时候也可能会单独做判断,因此只能采用打表的方式进行单独判断。然而对于正负0的判断并不知道如何解决,通过上网查找维基百科中有1/+0=+Infinite, 1/-0=-Infinite,采用这种方法对正负0进行判断。修复方案如下,可以正确通过所有测试。

标准patch:

 


Chart-1

specification:(1)函数调用的返回值为null(2)对该变量解引用出现NullPointerException

bug说明:给数组变量添加元素之后获取数组中元素的数量不变

修复过程描述:(代码,用时5分钟,涉及单个函数)

根据测试函数,可以看出采用的是增量测试,最开始其中的元素数量是0,然后向数组中添加元素,然后测试元素的数量是否更新。最初的想法是在添加元素的时候可能会触发一些变量的更新,单步调试发现并不是在添加元素的时候,而是在获取元素的数量时重新计算。在计算的函数中最开始是对一些特殊的条件的判断,比如没有plot对象时,直接返回为空数组,接下来很容易发现的错误是当plot对象不是null,并且其保存的dataset变量也不是null的时候依然返回了空数组,不合逻辑,因为根据测试代码,在dataset中添加元素,返回统计的信息应该是更新的,与这里直接返回空数组不符合,修改很简单,如下所示。正确通过所有测试。

标准patch:

 

Chart-2 (regression error:2)

specification:(1)当传入的数据不全是NaN时,maximumminimum不可以是NaN,应该是其中的数据(2)根据另外两个测试输入,maxmnumminimum的值可以来自数组中任何一位数据

bug说明:求区间的最大和最小值时,最大和最小值两个标志量没有初始化,同时每次比较的时候只比较最大或最小,比较不完全,还有另一个数据没有加入考虑

修复过程描述:(源代码,用时30mins,涉及单个函数)

运行测试根据获得的最大和最小值不正确,很容易定位到求取区间最大、最小值的函数上。阅读代码以及结合当输入的参数是[1.0,NaN,NaN]时返回值是null,单步调试可知此过程中minimunmaximun两个变量在计算过程中没有被赋值,原因是比较的值是参数List中的第二和第三个参数,而期望的返回值是1.0,和List的第一个参数相同,因此想到当List存在数据时应该使用List中的数据首先初始化一下,而因为这个初始化应该放在循环中,需要只能初始化一次,故需要添加判断条件来确定只初始化一次,因此生成的修复如下代码:

但是在运行其他的测试的时候发现,当输入的参数为[NaN,xx,xx]时,输出的结果也是不对的。再次单步跟踪调试发现上面的修复是minimummaximum的值为NaN,在比较的时候不能更新,因此继续添加限定条件如下修复:

修复之后之前的测试通过了,但是还有测试未能通过。当测试输入的参数是[1.0,NaN,0.5]时,期望的最大值和最小值分别是1.0和0.5,但是原来的代码中最大值只会和第三个数比,最小值只会和第二个数比,因此返回的最大和最小值都是1.0。所以,根据要求知道,最小值也会和第三位的数比较,同理最大数应该也和第二位的数比较最后的修复如下,所有测试通过。

标准patch:

 

Chart-3

specification:(1)对于TimeSeries类型中的变量maxYminY是由对象中的数组元素决定的(2)克隆之后数组元素被重新初始化但是maxYminY没有变化

bug说明:TimeSeries复制之后最大值没有更新

修复过程描述:(源代码,用时10mins,涉及单个函数)

运行出错函数发现原始的对象中的最大值是102,但是只复制了其中部分元素,最大值不包含在里边,正确的答案应该是101。单步调试寻找对象最大值和最小值的更新位置,发现在每次添加元素的时候会重新计算,因此猜测复制之后的对象没有调用更新函数。继续单步调试,发现运行了更新函数,但是最大值没有变,由于是复制过去的,因此其最大值保持着原来的最大值102,更新之后还是最大值。因此想到,当复制元素的时候应该将这些标志量重新初始化。因此在复制函数中,当完成复制还没有添加元素的时候首先初始化这些最大最小值。修复方法如下:

标准patch:

 

Chart-4

specification:(1)函数调用的返回值为null(2)解引用导致`NullPointerException

bug说明:NullPointerException

修复过程描述:(代码,用时5mins,涉及单个函数)

由于是NullPointerException异常,属于crash错误,单独运行出错的测试程序,可以直接定位到出错的语句,找到出现空指针错误的变量。根据提示,出现空指针,是因为没有对其是否为null做判断就直接调用了对象的函数。最简单的方法就是对该变量的引用添加判断条件,判断其是否为null,当其不是null是执行相关的操作,当是null时,根据下面的代码发现不会影响继续执行,但是否应该做其他的处理还推测不出来。所以首先采用的是简单的修改方案,直接添加一个空指针判断。代码如下,结果再运行测试顺利通过,至于其为null时是否应该再做其他的操作不是很清楚。

标准patch:

 

Chart-5

specification:(1)在往数组中添加元素的时候对下标访问出现负数(2)运行到此处,index应该是负数(3)实际index是正数导致异常

bug说明:IndexOutOfBoundException

修复过程描述:(代码+注释,用时5mins,涉及单个函数)

根据运行测试的输出是IndexOutOfBoundException,属于crash错误,根据输出的stacktrace很容易定位到错误的具体位置。发现是在往List中添加元素时传入了一个负值导致错误。然后根据上面的注释信息(negative index),当到达此处的时候 index 应该是负数,因此修复如下代码,通过测试,注释信息参考如下。

标准patch:

 

Chart-6

specification:(1)判断两个对象是否相同调用的是Objectequals()方法(2)判断的类型是lib中的类,不能修改其equals()函数(3)期望的结果是判断两个对象的值是否相同

bug说明:复制之后的对象和原来的对象不相等

修复过程描述:(代码+注释,用时30mins,涉及多个函数)

根据代码和测试结果,复制之后和复制之前不同。首先确定不相等的原因是什么,最开始怀疑的是复制的结果和原来的结果就是不一样的,即复制过程存在问题。因此开始单步调试复制的过程,结果发现过程比较繁琐,而且涉及到的都是一些系统的函数,比较难调试和观察。因此换做一种简单的方式,不看复制的过程,直接观察复制之后的对象和原来的对象,人工对比其对应的变量,结果发现却是一样的,但是判断的结果是两个对象不一致,因此应该是equals()函数存在错误。手动添加了一行l1.equals(l2)代码,作为调试使用。然后再单步调试。当比较两个Line2D$Double对象的时候最终判断结果跳转到了Object.equals()方法,根据注释信息,只有o1==o2的时候才会返回true,同时又由于Object的代码是不能修改的,所以问题不在这。但是根据调试的过程中调用的是Line2D.equals()函数,却跳转到了Object.equals()函数,说明Line2D类没有重写该函数,所以修复方法就是为Line2D类添加一个equals()函数。又发现Line2D也是系统库中的类,不能修改。所以只能再往上一层追溯对象比较的位置,最终发现在调用不同类的equals()函数前都是由一个ObjectUtilities.equals()函数进行分发的,因为对象的本身代码不能修改,只能在外边进行特殊判断了。修改代码如下,顺利通过测试。

标准patch:

 

Chart-7

specification:(1)计算maxMiddleIndex的函数中,在计算minMiddleIndex时只用到了下标minMiddleIndex(2)在计算maxMiddleIndex的时候只用到了minMiddleIndex

bug说明:在更新对象的元素的时候,对象的MaxMiddleIndex没有正确更新

修复过程说明:(源代码,用时10mins,涉及单个函数)

根据错误的测试,是在添加了元素之后获取对象的MaxMiddleIndex不是期望值。首先的反应是获取该值的时候计算出错getMaxMiddleIndex(),进入函数发现直接返回了对象的变量值,没有计算过程。然后观察测试函数只调用了一个add()方法,因此判断计算过程应该由该函数引发,错误应该在该函数内或者内部调用的其他的函数。进入后发现其中调用了两个函数,一个是updateBounds(),另一个是fireSeriesChanged()。根据名称两个函数都有可能。按顺序进入第一个函数updateBounds(),里边计算的都是关于max...min...的变量,其中就有minMiddleIndexmaxMiddleIndex的计算过程,两个过程紧邻,除了一些计算的细微差别之外其他之处几乎一样,对比两个计算过程非常容易发现,在计算minMiddleIndex的时候只用到了minMiddleIndex,而计算maxMiddleIndex的时候没有用自己反而是用了minMiddleIndex,根据两个变量的相似性很有可能是不小心代码敲错了。将里边的minMiddleIndex都改成maxMiddleIndex后运行测试,成功通过。

标准patch:

 

Chart-8

和Lang-8相似,参数没有被使用,违反了通常的使用模式。

标准patch:

 

Chart-9

specification:(1)对时间进行截取时,起始时间比结束时间晚(即index更大)(2)不满足要求的index传入另一个时间计算的函数中导致异常(3)获取上下界的函数中存在标志量emptyRange用来标识截取的区间是否为空

bug说明:对TimeSeries进行截取区间时上下界不合法

修复过程描述:(代码,用时20mins,涉及单个函数)

运行错误的测试,发现出错的原因是传入的两个参数startIndexendIndex不满足大小关系,根据此,逆向寻找传入参数的由来。在复制函数createCopy()函数中(根据代码似乎是复制一个时间对象,其时间区间给定),首先对startend做了大小比较检查,然后在想要复制的对象中进行搜索,当搜索返回的是负数时对其进行+1后取反处理,不同的是endIndex取反之后比startIndex小了,导致传入另一个复制函数中出现异常。进入另一个函数发现其对参数的检查是合理的,endIndex不应该小于startIndex,否则接下来的循环不能执行。因此确定出错的位置是在调用该函数之前,即搜索之后的检查。根据代码,有一个标志是用来标注是否复制的内容为空的变量empyRange,当startIndexendIndex小于0的时候该标识为true。现在的情况是传入的参数想要复制的时间段是合理的,即起始时间和结束时间满足先后顺序,但是都不在想要复制的对象中,此时应该返回一个空的区间,而不是抛出异常。依据此,生成如下的修复代码,通过测试。

标准patch:

 

Chart-10

specification:(1)将字符串转换为ToolTip信息的时候期望输出的结果是字符串中的\"转换为&quot

bug说明:生成URL字符串不满足要求

修复过程描述:(代码,用时2mins,涉及单个函数)

根据出错测试函数定位到生成ToolTip的一个函数存在问题,阅读代码发现其将字符串转换为Html字符串时,\"字符期望转换的结果是",进入转换函数内部发现其转换的过程非常简单,就是将传入的字符串转换添加了前缀和后缀,对传入的字符串并没有做解析。根据测试的输出要求,很容易修改成如下的代码,通过测试。

标准patch:

 


Closure-1

specification:(1)测试输入的jsp代码中函数的参数被删除导致错误(2)删除的参数在函数体内没有被引用(3)无用的函数参数是否被优化取决于对全局变量是否优化

bug说明:对JavaScript代码进行编译之后删除了没用的函数参数列表

修复过程描述:(代码+注释,用时5h20mins,涉及多个函数)

根据运行测试的结果,对于代码window.f = function(a) {};进行编译后,获得的语法树丢失了参数a的信息,猜测应该是在编译过程中对语法树的构建存在错误。由于并不是crash错误,不能定位具体的出错位置,因此只能使用单步调试的方法找到可能出错的具体位置。调试采用由粗粒度到细粒度逐步精化,缩小搜索空间的策略。首先对编译后的代码的AST树进行人工对比,但是由于树的结果太大,进行两个AST树的各个节点比较花费了很长时间,但是太乱没有找到问题。因为AST树的结构比较复杂,最后是比较两个AST树是否相等的,具体是哪里不相同应该先确定,然后再寻找构建AST树的过程中针对该部分的解析。通过单步调试,发现最后的比较只比较树中的firstnext两个域的值,然后再次比较两个AST的结构,这次只关注上面的两个域,进行比较,然后在纸上画出两个AST树的结构,发现在参数部分编译之后的ASTfirst域为null,而实际上应该是存在一个参数a。根据测试代码,确定出错位置在compile的过程,然后单步调试此过程。在调试的过程中,根据之前画下的AST结果,每运行一条语句都观察一下最后保存AST的变量的值的变化,如果运行某条语句后该变量的对应位置被修改了,再对这条语句进行细化跟踪(这是后来采用的调试策略,开始调试的时候过于细致,导致浪费了很长时间,中途放弃采用这种调试方式)。最后确定了出错的位置是最后的optimize函数,想到编译优化的内容,删除无用的变量。然后单步调试优化的过程,同样采用上面的策略,最后定位到了traverseAndRemoveUnusedReferences(Node)函数,但是该函数中调用了两个值得怀疑的其他的函数removeUnreferencedVars()removeUnreferencedFunctionArgs(),根据函数的名称,最先怀疑的是第二个函数,同时根据调试也是因为第二个函数的执行导致参数被删除。进入函数内部阅读详细的代码没有发现任何问题,不知道该如何修改。以为在对编译优化的设置上会有标志量,进入option的设置项中发现里边的标志量非常多,但是通过名称没有发现和函数的参数的优化相关的标志量。因此怀疑是之前的步骤不应该将变量a添加到无用变量列表中,接着单步调试之前的代码,同样没有发现。最后选择详细阅读代码以及代码注释信息,其中在函数removeUnreferencedFunctionArgs()中有这样的注释信息:

根据上面的注释信息可以发现,作者认为“如果用户删除全局变量,那么就可以删除函数的无用参数”,阅读代码根据名称获知标志量removeGlobals是用来标志是否删除全局变量的。因此修复如下,成功通过测试。

标准patch:

 

Closure-2

specification:(1)函数调用的返回值为null(2)解引用导致NullPointerException(3)当变量为null时,接下来的处理是没有意义的(对变量的域进行遍历)(4)函数没有返回值

bug说明:NullPointerException

修复过程描述:(代码,用时5mins,涉及单个函数)

因为是crash错误,根据stack trace信息直接定位到出错的语句,观察代码发现是代码对函数调用返回的结果没有做null检查直接引用导致错误。然而根据接下来的代码发现,当返回的对象是null时,下面的代码等同于没有计算,是对其元素的遍历,所以简单修复直接就返回就好了,运行测试程序,成功通过。代码如下:

标准patch:

 

Closure-3

specification:(1)inline变量导致catch语句块中的变量在catch语句块之外被引用(2)catch语句中的变量不应该被inline(3)被inline的变量依赖catch语句块中的变量

bug说明:在对代码编译优化时错误的inline了变量

修复过程描述:(源代码+注释,用时3.5h,涉及多个函数)

根据测试运行结果,非crash错误,需要单步调试定位错误的位置。使用由粗粒度到细粒度的调试方法逐步过滤,最后锁定到了FlowSensitiveInlineVariables类中的enterScope函数,该函数的作用是将一些可以inline的变量实现inline。然后对该部分代码进行单步跟踪调试,在调试的过程中基本理解了其大体的过程,首先是收集程序中的所有变量,然后通过正向和逆向的数据流分析过滤出可能会被inline的变量,同时也会分析变量的依赖关系,并且程序还会收集一些特殊的变量,比如在catch语句中的变量会被过滤掉。最后再将要inline之前,首先对变量的各种特殊情况进行判断,比如是不是函数的参数、是否依赖已经被inline的变量等。通过调试以及观察判断过程中保存的数据信息,发现针对下面的测试:

其中变量a依赖于err,而return语句已经超出了变量err的作用域,而代码中并没有发现对此进行判断。因此正确的修复应该添加对此判断。而调试的过程中发现其作用域Scope都是最大的域:整个函数(或许判断了但是由于相同没有排除掉)。所以通过作用域似乎不可行。因此采用了另一种比较取巧的方式。之前说到程序对catch做了特殊处理,所以这里我也采用了同样的处理方式,就是如果变量依赖于catch语句中的变量就不允许被inline,有点取巧,但是测试正确通过。修复代码如下:

标准patch:

 

Closure-4 (regression error:1)

specification:(1)在判断两个类型是否具有继承关系时获取类型实现的接口时返回的是自身导致递归死循环(2)期望的结果是抛出异常

bug说明:在比较两个类型是否有继承/实现关系的时候出现死环

修复过程描述:(代码+注释,用时2h,涉及单个函数)失败

根据错误提示信息属于栈溢出,根据经验多数出现在递归调用出现缺少退出条件判断的情况,通过调试定位出错的位置发现在类型检查的过程中判断两个类型是否具有继承/实现关系的时候,获取类型实现的接口时返回的是自己本身,导致循环调用栈溢出。所以想到的修改方案是将获取到类实现的接口和自己比较,如果实现的接口和自己相同,即形成环则退出,不再进行递归。修复代码如下:

然后再次运行刚刚的测试,发现没有了栈溢出错误,但是还存在错误。根据测试的注释信息,该测试是期望出现解析错误,识别出有继承环Parse error. Cycle detected in inheritance chain of type MyType。根据提示的错误信息进行单步调试试图发现对实现的接口的处理,但是当处理annotation信息时并不知道当前的具体类是什么,所以没有定位到具体应该在哪里修复,修复失败

标准patch:

 

Closure-5

specification:(1)删除一个变量和删除变量的一个属性是不等价的(2)被inline的变量是其中一个对象的属性(3)语句中存在删除该属性的操作

bug说明:删除一个变量和删除变量的属性是不等价的,存在变量属性不能被inline

修复过程描述:(代码+注释,用时2h,涉及多个函数)

根据测试的输出信息以及测试函数的名称获知,源代码中的变量不应该被inline。通过debug过程定位出错的函数应该出在InlineObjectLiterals类的afterExitScope函数。根据函数代码的命名情况,首先怀疑出错的函数可能是isVarInlineForbidden函数出错,该函数是判断当前的变量是否被忽略,即不被inline。进入该函数阅读代码发现,注释信息说明:当变量是作为输出(用于返回值)的不应该被inline,结合测试的代码中存在return语句怀疑错误就是因为没有对语句是否为return语句进行判断。因此想在该处进行判断,通过调试并输出程序状态发现该函数中的变量不包含完整的return语句,只是一个变量,因此不能判断是否被用于return语句中。返回afterExitScope函数,发现另一个值得怀疑的函数isInlineObject(),判断当前的变量是否被inline。同时注意到一点传入该函数的参数是对当前变量的引用信息。进入函数内部阅读代码,大体内容就是对变量的引用进行检查是否能使用变量进行替换。在此过程中获取到了变量的引用处的节点信息、父节点信息以及祖先节点信息。debug输出这些内容,发现能够获得其所在的return语句的完整结构。然后根据代码发现对其父节点和祖先节点的类型都有判断。其中注意到,当该变量在return语句中被引用时,其祖先节点是完整的return语句。结合代码对祖先节点的判断,生成如下的修复:当变量被引用的位置是return语句时,不被inline。

运行当前的测试顺利通过,然后运行其他的测试发现出现了新的失败测试。因此怀疑刚刚的修复是否正确。又仔细阅读代码和注释信息,在注释中发现这样一句话:

删除变量的属性和删除变量是语义不等价的,而代码中对变量的引用中存在一处删除属性语句delete foo.bar。但是对刚刚的return语句就不考虑了吗?想想return的说明在另一个函数中,注释信息跨函数似乎不太可能,因此根据注释信息还原了上面的修改,生成新的修复代码如下:

再运行测试,成功通过。

标准patch:

 

Closure-6 (regression error:2)

specification:(1)将类型不一致的值赋给变量时期望抛出warning(2)判断用于赋值的变量类型是否合法时调用了expectCanAssignTo()函数(3)未通过测试调用该函数返回值是`false

bug说明:变量的声明类型和赋值变量类型不一致没有report warnings

修复过程描述:(代码,用时3h,涉及多个函数)

根据输出的测试错误信息,结合测试的输入数据,发现错误的原因是变量的声明类型和赋值类型不一致,期望能够以warning的形式报告。因此单步调试找到对该语句中的变量类型进行检查的代码段。最后定位到TypeCheck类的visitVar函数,在该函数中是调用了validator.expectCanAssignTo()函数返回一个boolean值表示该赋值类型是否匹配,调试输出发现输出结果是false,因此猜测可以在此处判断是否满足赋值的类型一致性,如果返回false则报告warning,但是如何report?在该类的上下文中的其他函数中找到了一个report()函数,可以记录当前的warning信息。因此添加了一个report函数调用,修复代码如下:

结果再运行测试的时候发现抛出了NullPointerException,单步调试发现传入的DiagnosticType应该在事先定义好并添加到DiagnosticGroup中,否则在report的过程中会去查找是否存在该DiagnosticType的定义,因为没有定义,导致NullPointerException错误。因此,仿照之前代码的定义格式,自己定义了一个变量,代码如下,

并将其加入到了All_DIAGNOSTICS中。再次运行测试时发现还是错的,原因是输出的warning信息内容不对。返回修复的代码处发现函数validator.expectCanAssignTo()函数传入的参数和输出的一致。所以判断应该是在该函数中修复,将上面的修复内容复原。进入validator.expectCanAssignTo()函数阅读代码发现其中调用的一个mismatch函数就是用来report warning的,而当其中判断条件if语句满足时只调用了registerMismatch函数,没有调mismatch。根据函数的名称前一个是注册,第二个应该是报告,所以在注册之后应该再报告。修复代码如下:

再运行刚刚的测试,顺利通过。但是存在其他的错误测试,发现是同样的问题,很快找到对应的函数validator.expectCanAssignToPropertyOf()函数,同样if语句中少调了mismatch函数,同样修复方法,顺利通过测试。

标准patch:

 

Closure-7

specification:(1)类型 Object|boolean|number|string期望的解析结果是不变(2)实际运行删除了Object类型(3)调试发现该Object缺失是由于函数caseObjectType()的返回值为null导致的(3)根据注释Object可能是function的子类,当约束ObjectFunction时(存在一个标志量),保留子类,否则过滤掉function的子类

bug说明:对于类型的过滤,Object类型存在两种,一种是实现了Call方法的,另一种是没有实现Call方法的,在过滤类型时需要对其进行特殊考虑

修复过程描述:(源代码+注释,用时2h,涉及单个函数)

根据测试输出结果,其输入的类型是Object|boolean|number|string,期望输出的结果也是Object|boolean|number|string,而实际输出的结果却是boolean|number|string,导致assert错误。通过调试发现代码对每一个类型都会进行单独处理,而处理的函数却不一样。根据上面的输出只需要对Object类型的处理进行调试即可。单步调试发现最终决定是否保留该Object类型是根据函数caseObjectType()的返回值。其中涉及到一系列的条件判断。代码如下:

单步调试发现该测试输入的type=Object,会进入到iftrue分支,而且获取到的ctorTypeFunction,而当resultEqualsValuetrue的时候该语句执行正确,可以通过测试,当其为false时导致过滤掉了Object类型,因此首先想到的是应该对resultEqualsValue进行分类处理,这样才不会影响之前的正确测试。因此修复的代码框架为:

上面的代码可以保证当resultEqualsValue=true时依然可以正常通过测试。但是上面的????处应该如何填写却成了难题。最后依照上面的注释信息,意思是如果Object被约束为“Function”时,保留其子类,只过滤掉“function”的子类。但是没有读懂到底是什么意思。然后就寻找"Function"和“function"的区别是什么。在调用的上一层函数中的注释中找到如下说明:

上面的注释信息详细的说明了各个参数的含义。因此得知,Object类型可以实现"Call",也可以不实现,但是两者是不一样的。再理解上面的两条注释,猜测上面的两句话应该是说的两种情况(因为连不到一起):情况一,当resultEqualsValuetrue的时候保留其(‘function’)子类。情况二,当resultEqualsValuefalse的时候过滤掉”function“的子类。再结合调试的时候的类型信息,修改上面的修复代码如下,通过测试。

标准patch:

 

Closure-8

specification:(1)编译器对代码进行优化,合并变量的声明(2)测试结果将函数参数合并到了局部变量的声明中(3)期望的结果是该变量不被合并声明(4)根据编程知识当函数中声明相同名称的变量会将参数覆盖(5)优化过程首先是搜集可能会被合并声明的变量(6)还有一个列表用来保存不能被合并声明的变量

bug说明:对变量声明进行合并优化时将函数的参数没有特殊考虑,进行声明合并导致二次声明

修复过程描述:(源代码,用时30mins,涉及单个函数)

根据测试输出的结果以及之前的调试经验,很快定位到对变量进行声明合并优化的函数。其过程分成两步,首先是搜集可以合并的变量,然后再执行合并的操作。根据测试很明显,函数的参数不应该出现在合并的列表中。因此错误应该处在搜集的过程。单步调试,发现在对变量进行判断是否能合并声明时进行了一些判断,但是当到函数内部的时候并不能获取当前的变量是否是在函数参数列表中。所以应该是有一个标志量来标志一些变量不能被合并声明。阅读代码发现在判断的过程中有一个集合保存的变量会被排除掉,不被合并。因此想到的方法就是在遍历到函数的参数列表时对参数变量进行标记,即添加到该集合中,在之后访问同样的变量时就不会被合并了。依据此,生成如下的修复代码,成功通过测试。

标准patch:

 

Closure-9

specification:(1)函数只考虑了/字符作为地址的分隔符(2)输入的地址可能使用\作为分隔符(2)程序没有考虑\分隔符导致出错

bug说明:对字符串中的\\没有处理,只处理了/字符,导致Model名称解析错误

修复过程描述:(源程序+注释+上网,用时10mins,涉及单个函数)

根据测试输出发现对Model的前缀并没有处理。调试发现到函数normalizeSourceName中是将之前输入的前缀直接截取掉。而前缀中的分隔符采用的是/符号,根据注释说明:The DOS command shell will normalize "/" to "\", so we have to wrestle it back,因此很容易想到的修复代码如下:

再次运行测试结果在Regex编译的时候出错,上网查找原因是在java中字符串中的\字符需要转义,同时在Regex翻译过程中\需要再次转义,因此,想要匹配\字符需要两次转义,修复代码如下:

成功通过测试。

标准patch:

 

Closure-10

specification:(1)编译优化时对数值运算会进行编译预计算优化(2)jsp代码中的+不一定是数值运算,可能是字符串的连接运算(3)当此处的+是字符串的连接运算时不能进行预计算(4)当+的计算类型不确定时为了保证正确性不进行预计算

bug说明:对JavaScript代码的加法进行优化时,如果中间计算步骤可能出现String类型则不能进行加法先计算

修复过程描述:(源代码+注释,用时40mins,涉及单个函数)

根据测试输出信息,源程序对编译的代码进行了优化,将加法操作提前进行运算。根据输出结果是不应该被优化的,但是根据输出没有理解为什么不能优化。因此单步调试尝试理解其整个过程,在调试的过程中根据注释信息发现加法操作可能不是数值的运算,也可能是字符串的连接运算,

结合测试输入的数据,

不能判定前部分返回的是字符串'1'还是数值2。找到了原因开始找程序的出错位置。在调试过程中发现每次进行优化之前都会进行判断运算的两个部分有没有可能是string类型。根据调试结果对于前部分的判断是不可能。这明显是错的。基本定位到出错的位置,在判断节点的类型时。判断其是否为string类型时调用了一个函数mayBeString(),根据函数名称知道只要可能就会返回true。而上面的前部分明显可能string的。继续调试发现在该函数中调用了一个allResultMatch()函数,该函数会将节点进行分解判断是否每一子部分都可能是string类型,只有都是string的时候才会返回true。上面的节点判断时,'1'返回的是true2返回的是false,使得最终结果是false。很明显这里不应该取合取运算。而刚好就在该函数下面有一个函数叫anyResultMatch()函数,根据注释和代码其判定当前节点子节点有一个可能为string类型就返回true,即返回子结构的析取运算。很容易修改代码如下,成功通过测试。

标准patch:

 


Time-1 (regression error:1)

specification:(1)era类型在插入时间序列中被插入到year后面,违反单位从大到小排列(2)era类型的UnitMillis的值为0,导致和year比较时返回值为-1,实际期望的返回值为1(3)跟踪调试发现比较的过程中yearmonth等都有对应的DurationType,而era对应的却是UnsupportedDurationType

bug说明:对时间的单位不按顺序排序

修复过程描述:(源代码,用时30mins,涉及单个函数)

通过测试函数可以定位到函数出错的位置是compareTo()函数,比较两个时间单位的大小。根据之前运行的结果可以获得时间的排序是按照由大单位到小单位进行排列的(年,月,日)。而比较单位的大小关系是通过类的一个域unitMillis作为标记的。当获取世纪era的标记时发现返回的一直是0,其实际应该比年要大,结果却返回0.导致比较结果错误,即世纪小于年。因此猜测对于世纪可能需要特殊处理,因此对原来的比较函数进行修改如下:

然后运行刚刚的测试正确通过,但是导致其他的测试出现了错误,调试结果发现由于上面的修复导致。因此猜测上面的修复是错误的,但是观察到该测试获取到的otherMillis值也是0却正常通过了,了解到应该不是0的问题。然后通过调试比较两个测试在该步的不同点,发现原来的测试中世纪era在获取对应的DurationField时返回的是UnsupportedDurationField,根据名称猜测世纪本身在该比较单位中是不受支持的,因此针对上面的错误重新修改修复补丁如下:

标准patch:

 

Time-2

specification:(1)世纪(era)和年(year)在获取RangeDurationType时的返回值都是null被判定为同一类型抛出异常(2)实际两个类型是不同的单位

bug说明:世纪era和年year并列时由于获取getRangeDurationType都为null,导致错判为同一单位。

修复过程描述:(代码,用时20mins,涉及单个函数)

根据未通过测试,定位到exception被抛出的位置,单步调试发现在此处时由于获得erayearRangeDurationType返回的都是null被错判为相同的类型。结合测试的assert信息,era应该正确的被插入到该Partial中,而且根据调试观察,其插入的位置也是正确的,因此判断就是在抛出异常的位置判断不够全面导致错误,原本不是一个类型但是其RangeDurationType信息都是null导致出错,修复方法是添加判断条件,代码如下,成功通过测试。

标准patch:

 

Time-3

specification:(1)在时间的加法函数add()中,当传入加的参数值为0的时候会由于做时间的调整导致出错(2)期望的结果是保持原来的时间不变

bug说明:在原来的时间基础之上加上0时长会出现错误

修复过程描述:(代码+注释,用时15mins,涉及单个函数)

根据未通过测试发现当为日期加一个0时会出现与原来不相等,通常来说在计算的过程中应该一直保持原来的时间不变,因此单步调试发现在进行加法运算时,对原来的时间首先加了一个时长然后进行运算,考虑为什么需要添加这个时长呢?根据注释信息说明,

虽然并没有很理解,但似乎是有意义的,因此不对其原来的计算方式进行修改,而是对0做特殊的判断。修改如下,成功通过测试。

标准patch:

 

Time-4

specification:(1)函数对时间进行重新赋值的时候,原本的类型是hourOfDay,而传入的参数的类型是clockhourOfDay(2)两个类型表示的是同一个时间单位(3)两个类型在时间单位的表中的index不同(4)期望的结果是抛出IllegalArgumentException

bug说明:同样的计时单位具有不同的index导致更新的时候会出现重复错误

修复过程描述:(代码,用时15mins,涉及单个函数)

根据测试函数正确的运行结果应该是抛出异常,单步调试发现原来的计时单位中有hourOfDay,而传入的参数是clockhourOfDay,虽然指的是同一个时间单位,但是是不同的表示方法,导致其index不同,所以没有更新原来的时间而是重新添加了一个新的计时单位clockhourOfDay,产生重复。因此想到的是如果更新时间信息传入的参数要么和原来的计时单位是一致的,要么传入的参数在原来的计时单位中不存在,不能同一单位用不同的表示方法。因此修复方法就是检测是否存在表示的是同一计时单位却不是同一个,修复代码如下,通过测试。

标准patch:

 

Time-5

specification:(1)对时间进行设置的时候由于不支持对应的类型导致出错(2)传入的参数是年份和月份信息(3)函数注释中说明可能会抛出UnsupportedOperationException异常

bug说明:在将时间进行格式化的时候当不支持的单位出现时没有对其进行转换导致出错

修复过程描述:(源代码+注释,用时30mins,涉及单个函数)

根据测试函数,对时间进行标准化的时候原本的时间是按年,月,周计时的,但是标准化的格式要求没有年,而是使用月,周,天进行表示。通过单步调试发现在设置对应单位的数值之前并没有对该单位是否支持进行判断(比如该例子中对年是不支持的),从而导致异常出现。知道了这一点就很好修改了,只要在计算之前确定对应位是否受支持,然后有目标的进行转换就可以了。然后根据注释中的说明,如果由于类型不受支持导致最终的年份或月份不能完全被设置到period类型中,应该抛出UnsupportedOperationException异常。修改的代码如下:

标准patch:

 

Time-6 (regression error:2)

specification:failed(1)对于时间的加法和减法运算,可能会跨公元前后(2)不存在公元0年(3)当从公元前变到0年时应该是公元1年,当从公元后变到0年时应该是公元前1年

bug说明:时间相加时跨公元前后导致出错

修复过程描述:(源代码,用时2h,涉及多个函数)失败

根据测试的数据很容易发现当公元前的年份加上一个负数导致跨公元时由于没有对0做特殊调整,根据单步调试的信息,发现在加函数中plusYears()有对加的年份是否为0进行判断,其他的没有判断,猜测在此处进行特殊的判断,修复代码如下:

运行刚刚的测试函数顺利通过,但是运行其他的测试函数出现了新的错误,通过调试发现原因就是上面的修改造成的,因此恢复上面的修改内容。继续运行未通过的测试调试,未能确定修改的位置。然后找了一些类似的测试但是正确通过的,调试运行,发现一些其他的测试通过时执行了条件的另一个分支,因此怀疑其中的一个分支代码存在问题,尝试以下的修复:

运行刚刚的测试同样通过了,但是又出现了新的测试错误同样由上面的修改引起,因此修改也是错的。根据测试的对比以及代码和理解注释内容,最终未能确定修改的位置。修复失败

标准patch:

 

Time-7

specification:(1)输入的年份是2014年,解析之后变成了2013年,导致2月19日异常

bug说明:在对时间进行解析的时候错误的修改了当前的时间,导致解析错误

修复过程描述:(源代码,用时30mins,涉及单个函数)

单步调试未通过的测试,在单步跟踪的过程中发现解析的日期是2月29日会被认为是不合法的,因此反过来看当前的输入信息是2014年,应该是闰年,所以应该是最大值的获取或者年份解析有问题。通过调试发现2014年在解析的过程中转换成了2013年,导致错误的。然后看年份解析过程在解析之前对时间进行了一次调差long instantLocal = instantMillis + chrono.getZone().getOffset(instantMillis);。开始以为是将时间调整到标准时间,但是发现时间格式化类中的Zonenull,再仔细理解发现其中存在问题,在调整时间差的时候用的是自己本身与标准时间的时差,然后在获取年份的时候用的还是自己本身的时区,而时间却用了标准时区的时间,自相矛盾。修改方法是转换全部时间到目标时区进行计算,但是根据测试只给了Locale信息,获取不到TimeZone信息。因此采取了简单的修复方案,直接在原来的时区进行计算,代码如下。可以通过测试。

标准patch:

 

Time-8 (regression error:1)

specification:(1)函数传入的参数是两个整数,分别代表时间的小时 h 和分钟 m(2)h>0,m<0 应该抛出IllegalArgumentException(3)h==0,结果的符号和m的符号保持一致(4)h<0,结果的符号为负

bug说明:时间转换的时候关于符号的处理判断错误

修复过程描述:(源代码+注释,用时20mins,涉及单个函数)

根据未通过的测试函数跟踪调试发现当输入的参数是hour=0,minutes=-xx时,会出现判断分钟不合法。而结合测试的assert信息,其输出应该满足保持符号正确解析。因此对原来的程序进行下面的修改:

再次运行测试发现原来的assert通过了,但是另一个assert出错。观察输入是当hour>0,minutes<0时期望的结果是抛出异常。结合注释信息-23<=hour<=23,-59<=minutes<=59,确定上面的修改没有错误,但是根据注释中提供的示例:

通过对上面的示例进行分类总结发现除了上面的hour,minutes的bound外,当输入的参数满足hour>0 && minutes<0时也会出现异常。因此对上面的修复进行修改如下:

再次运行测试,结果又出现了新的assert错误,-2, -15被解析成了-1:45,正确的结果是-2:15。同样根据注释信息以及程序的计算过程(将小时换算成分钟再计算),比较上面的注释信息发现当hour<0的时候minutes的符号对结果是没有影响的,然而在计算的过程中涉及到应该加还是减。所以根据注释信息很容易分析出,当minutes>0时应该做减法,因此对该函数的另一处计算过程修改如下,成功通过所有测试。

标准patch:

 

Time-9

specification:(1)函数的参数hourOffset的合法取值范围是-23~23(2)测试输入的参数是24,期望的结果是抛出IllegalArgumentException

bug说明:对时间解析时小时不能超过-23~23范围

修复过程描述:(代码+注释,用时2mins,涉及单个函数)

根据未通过测试,发现当输入的时间小时为24时应该抛出异常。单步调试发现在解析函数中没有对该值进行判断。根据注释提供的说明,输入的参数hour应该在-23~23范围之内。因此很容易生成下面的修复代码,成功通过测试。【注意这个和上面的Time-8是同一个函数错误,但是其函数的要求已经不一样了。在Time-8中minutes是可以为负数的,而在Time-9中注释中说明必须在0-59之间,因此函数的计算部分的判断也不一样了

标准patch:

 

Time-10 (regression error:1)

specification:(1)根据测试计算2月1日和2月29日相差的月份(2)输入的参数中没有包含年份信息(3)在计算的过程中程序默认的是1970年(4)由于1970年是平年2月没有29日导致抛出异常(5)期望输出的结果是相差0个月(6)根据输入和期望输出的结果我们在不指定年份的时候我们应该认为2月有29日是合法的

bug说明:在比较两个日期时由于没有年份信息,默认为1970年,导致2月29日出错。

修复过程描述:(代码+注释,用时1h,涉及多个函数)

根据测试输出的错误信息,定位到当判断日期时对2月29日判断出错。单步调试发现在计算的过程中没有涉及到年份的信息,导致默认年份是1970年,平年没有2月29日。但是根据代码知道其目的只是为了比较两天的时间差,并没有指明具体是哪年。因此2月29日在不指定年份的情况下根据测试应该是合理的,不应该产生异常。因此想到的解决方法是设置年份信息。但是没有找到设置的位置。根据调试的过程发现其中调用了很多公共的接口函数,因此不能对获取月份的最大日期进行修改,否则可能会导致其他的错误。修改方案应该是在最初调用的几层涉及到ReadablePartial类的函数中进行修改,因为在这些函数中能够获取到是否有信息。而根据函数中引用的变量情况最后确认只存在两个函数中可以修改,一个是between()函数,另一个是set函数,其传入的变量都是ReadablePartial,再继续跟踪其他函数不能获取到调用该函数时日期中是否含有年份信息。结合抛出异常的位置,最后选择了距离异常最近的函数set进行修改,修改方法就是对其进行特殊判断:如果没有年份信息,就设置一个闰年,但是根据代码没有设置年份的接口。阅读代码知道该函数是计算了当前日期到1970年的时间差,采用了一种比较取巧的方法,修改代码如下:

采用上面的方法测试没有异常产生,但是最后的结果不正确,因为系统默认的年份信息并没有改变,当把时间添加回来之后导致最后结果2月29日比2月1日大一个月,原因是在1970年的2月28日基础之上加上了一天的时间导致系统认为是1970年3月1日。所以修改不正确,看来只能通过修改当前的年份信息。而修改之处只能是set函数和between函数,又set函数不能设置年份信息,最终将修改位置定位到between函数中。又仔细阅读代码发现在调用set函数时传入的参数有两个,根据注释说明,第二个参数是用来被更新的,相当于目前的基准时间。根据set函数的执行过程了解到计算时获取到的时间差是在传入的参数基础之上更新的,目前传入的参数是0L,也就是1970年起始时刻。因此想到如果将传入的参数设置到一个闰年的时间差就相当于在闰年计算。而如果获取这个时间呢?根据之前的调试知道set函数是获取对应日期的millis表示,所以将其复制到了between函数中进行相应的修改就可以了。故最后的修复如下,成功通过测试。

标准patch: