
嗨! 尽管2019年会议季节尚未结束,但我们想谈一谈过去会议期间我们在展位上为访客提供的Bug查找挑战。 从2019年秋季开始,我们一直面临着一系列新的挑战,因此我们现在可以揭示针对2018年以前的任务和2019年上半年的解决方案-毕竟,其中许多来自以前发布的文章,并且我们有一个链接或QR码,其中包含有关打印在我们的挑战传单上的各个文章的信息。
如果您参加了我们通过展位参加的活动,您可能会看到甚至试图解决我们的一些挑战。 这些是使用C,C ++,C#或Java编写的真实开源项目的代码片段。 每个摘录都包含一个错误,并且要求来宾尝试找到它。 成功的解决方案(或只是参与错误的讨论)将获得奖励:螺旋绑定的桌面状态,钥匙串等:
也要吗 然后欢迎在即将举行的活动中到我们的展位前来。
顺便说一下,在文章“
会议时间!总结2018 ”和“
会议。2019年上半年小计 ”中,我们分享了我们参与今年初和2018年举办的活动的经验。
好吧,让我们玩我们的“发现错误”游戏。 首先,我们将按语言分组分析2018年早期的挑战。
2018年
C ++
铬虫static const int kDaysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; bool ValidateDateTime(const DateTime& time) { if (time.year < 1 || time.year > 9999 || time.month < 1 || time.month > 12 || time.day < 1 || time.day > 31 || time.hour < 0 || time.hour > 23 || time.minute < 0 || time.minute > 59 || time.second < 0 || time.second > 59) { return false; } if (time.month == 2 && IsLeapYear(time.year)) { return time.month <= kDaysInMonth[time.month] + 1; } else { return time.month <= kDaysInMonth[time.month]; } }
解决方案在Chromium中发现的这个错误可能是最“长期运行”的挑战。 我们一直将其提供到2018年,并且还将其包含在多个演示中。
if (time.month == 2 && IsLeapYear(time.year)) { return time.month <= kDaysInMonth[time.month] + 1;
最后一个
If-else块的主体在return语句中包含拼写错误:
time.month是第二次而不是
time.day被意外写入。 此错误使函数始终返回
true 。 该错误将在文章“
2月31日 ”中详细讨论,并且是一个很酷的错误示例,该错误很难被代码审查发现。 这个案例也很好地说明了我们如何使用数据流分析。
虚幻引擎错误 bool VertInfluencedByActiveBone( FParticleEmitterInstance* Owner, USkeletalMeshComponent* InSkelMeshComponent, int32 InVertexIndex, int32* OutBoneIndex = NULL); void UParticleModuleLocationSkelVertSurface::Spawn(....) { .... int32 BoneIndex1, BoneIndex2, BoneIndex3; BoneIndex1 = BoneIndex2 = BoneIndex3 = INDEX_NONE; if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2]) &BoneIndex3) { .... }
解决方案这里要注意的第一件事是
VertInfluencedByActiveBone()函数的最后一个参数具有默认值,不需要指定。 现在以简化形式查看
if块:
if (!foo(....) && !foo(....) && !foo(....) & arg)
该错误现在清晰可见。 由于存在拼写错误,对
VertInfluencedByActiveBone()函数的第三次调用使用三个参数而不是四个参数执行,返回值然后参与
&操作(按位AND:左操作数是
VertInfluencedByActiveBone返回的
bool类型的值
( ) ,右操作数是整数变量
BoneIndex3 )。 该代码仍可编译。 这是固定版本(添加逗号,右括号移到表达式的末尾):
if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2], &BoneIndex3))
该错误最初是在文章“
虚幻引擎4的期待已久的检查 ”中提到的,其标题为“最好的错误”,我完全同意。
Android错误 void TagMonitor::parseTagsToMonitor(String8 tagNames) { std::lock_guard<std::mutex> lock(mMonitorMutex);
解决方案程序员对
if块条件下的操作优先级有错误的假设。 此代码无法正常工作:
if (ssize_t idx = (tagNames.find("3a") != -1))
将为
idx变量分配值0或1,条件是真还是假将取决于此值,这是一个错误。 这是固定版本:
ssize_t idx = tagNames.find("3a"); if (idx != -1)
在文章“
我们通过PVS-Studio检查了Android源代码,或者没有完美的方法 ”中提到了此错误。
这是另一个带有Android错误的重要挑战:
typedef int32_t GGLfixed; GGLfixed gglFastDivx(GGLfixed n, GGLfixed d) { if ((d>>24) && ((d>>24)+1)) { n >>= 8; d >>= 8; } return gglMulx(n, gglRecip(d)); }
解决方案问题出在
(d >> 24)+ 1表达式中。
程序员希望检查
d变量的8个最高有效位是否设置为1,但不是一次都设置为全部。 换句话说,他们想检查最高有效字节是否存储除0x00和0xFF之外的任何值。 首先,程序员使用(d >> 24)表达式检查最高有效位是否为null。 然后,他们将八个最高有效位移至最低有效字节,并期望最高有效符号位在所有其他位中重复。 也就是说,如果d变量的值为0b11111111'00000000'00000000'00000000,则在移位后它将变为0b11111111'11111111'11111111'11111111。 通过在
int值0xFFFFFFFF上加1,程序员期望得到0(-1 + 1 = 0)。 因此,
((d >> 24)+1)表达式用于检查不是所有八个最高有效位都设置为1。
但是,最高有效符号位在移位时不一定会“扩展”。 这就是标准所说的:“ E1 >> E2的值是E1右移E2位的位置。 如果E1具有无符号类型,或者E1具有带符号类型且非负值,则结果的值是E1 / 2 / E2的商的整数部分。
如果E1具有带符号的类型和负值,则结果值是实现定义的 。
因此,这是实现定义的行为的示例。 此代码的确切工作方式取决于CPU架构和编译器实现。 移位后最高有效位很可能最终为零,并且
((d >> 24)+1)表达式将始终返回除0以外的值,即始终为真值。
确实,这是一个不平凡的挑战。 像以前的错误一样,该错误最初在文章“
我们通过PVS-Studio检查了Android源代码,或者没有什么是完美的 ”中讨论过。
2019年
C ++
“都是海湾合作委员会的错” int foo(const unsigned char *s) { int r = 0; while(*s) { r += ((r * 20891 + *s *200) | *s ^ 4 | *s ^ 3) ^ (r >> 1); s++; } return r & 0x7fffffff; }
程序员将此错误归咎于GCC 8编译器。 真的是海湾合作委员会的错吗?
解决方案该函数返回负值,因为编译器不会为按位与(&)生成代码。 该错误与未定义的行为有关。 编译器注意到,
r变量用于计算和存储总和,仅涉及正值。
r变量不应溢出,因为那将是未定义的行为,编译器完全不必考虑。 因此得出的结论是,由于
r在循环结束时不能有负值,因此不需要操作
r&0x7fffffff来清除符号位,因此只需告诉函数返回
r的值即可。
在文章“
PVS-Studio 6.26已发布 ”中描述了此错误。
QT错误 static inline const QMetaObjectPrivate *priv(const uint* data) { return reinterpret_cast<const QMetaObjectPrivate*>(data); } bool QMetaEnum::isFlag() const { const int offset = priv(mobj->d.data)->revision >= 8 ? 2 : 1; return mobj && mobj->d.data[handle + offset] & EnumIsFlag; }
C#
Infer.NET错误 public static void WriteAttribute(TextWriter writer, string name, object defaultValue, object value, Func<object, string> converter = null) { if ( defaultValue == null && value == null || value.Equals(defaultValue)) { return; } string stringValue = converter == null ? value.ToString() : converter(value); writer.Write($"{name}=\"{stringValue}\" "); }
解决方案评估
value.Equals(defaultValue)表达式时,可能会取消对
value变量的空引用。 当变量的值
等于 defaultValue!= Null和
value == null时,就会发生这种情况。
此错误来自文章“
Infer.NET代码中存在什么错误? ”
FastReport错误 public class FastString { private const int initCapacity = 32; private void Init(int iniCapacity) { sb = new StringBuilder(iniCapacity); .... } public FastString() { Init(initCapacity); } public FastString(int iniCapacity) { Init(initCapacity); } public StringBuilder StringBuilder => sb; } .... Console.WriteLine(new FastString(256).StringBuilder.Capacity);
程序将在控制台中输出什么?
FastString类怎么了?
解决方案程序将输出值32。原因是传递给构造函数中
Init方法的变量的拼写错误的名称:
public FastString(int iniCapacity){ Init(initCapacity); }
不会使用构造函数参数
iniCapacity ; 相反,传递的是常量
initCapacity 。
在“狂野的西部最快的报告-以及少数的错误... ”
一文中讨论了该
错误。 罗斯林错误 private SyntaxNode GetNode(SyntaxNode root) { var current = root; .... while (current.FullSpan.Contains(....)) { .... var nodeOrToken = current.ChildThatContainsPosition(....); .... current = nodeOrToken.AsNode(); } .... } public SyntaxNode AsNode() { if (_token != null) { return null; } return _nodeOrParent; }
解决方案current.FullSpan.Contains(....)表达式中的
current的潜在空引用。 作为调用
nodeOrToken.AsNode()方法的结果,可以为
当前变量分配一个空值。
此错误来自文章“
检查Roslyn源代码 ”。
Unity错误 .... staticFields = packedSnapshot.typeDescriptions .Where(t => t.staticFieldBytes != null & t.staticFieldBytes.Length > 0) .Select(t => UnpackStaticFields(t)) .ToArray() ....
解决方案错字:使用
&运算符代替
&& 。 即使
t.staticFieldBytes变量为
null ,这也会导致
始终执行
t.staticFieldBytes.Length> 0检查,这反过来又导致空取消引用。
该错误最初显示在“
讨论Unity3D的开源组件中的错误 ”一文中。
爪哇
IntelliJ IDEA错误 private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2;
为什么程序会错误地计算大写单词的数量?
解决方案如果大写单词的数量少于20%,则该函数应返回
true 。 但是由于整数除法,该检查不起作用,整数除法仅求值为0或1。仅当所有单词都大写时,该函数才会返回
false 。 否则,除法将得出0,并且该函数将返回
true 。
该错误来自文章“
PVS-Studio for Java ”。
Spotbugs错误 public static String getXMLType(@WillNotClose InputStream in) throws IOException { .... String s; int count = 0; while (count < 4) { s = r.readLine(); if (s == null) { break; } Matcher m = tag.matcher(s); if (m.find()) { return m.group(1); } } throw new IOException("Didn't find xml tag"); .... }
搜索xml标记有什么问题?
解决方案计数<4条件始终为true,因为变量
计数在循环内不会增加。 xml标记本应在文件的前四行中搜索,但是由于缺少增量,因此程序将读取整个文件。
像以前的错误一样,该错误在文章“
PVS-Studio for Java ”中进行了描述。
今天就这些了。 快来看我们即将举行的活动-寻找独角兽。 我们将提供新的有趣挑战,当然还有奖品。 再见!