
你好 尽管2019年会议季节仍在进行中,但我们想讨论一下早些时候提供给我们展位访客的任务。 我们在2019年秋季开始时使用了一组新任务,因此已经可以发布2018年以及2019年上半年的旧问题的解决方案。此外,其中许多是从以前发表的文章中摘取的,任务传单中包含链接或QR码有关文章的信息。
如果您参加了我们在展台旁站立的会议,您可能会看到甚至解决我们的一些问题。 这些总是来自真正的开源项目的C,C ++,C#或Java编程语言的代码片段。 该代码包含我们建议访问者查找的错误。 对于解决方案(或仅是错误的讨论),我们提供奖品-桌面上的状态,小装饰品等:
你想要一样吗? 在下届会议上来到我们的展位。
顺便说一下,文章“
会议时间!总结2018年的结果 ”和“
会议。2019年上半年的中期结果 ”包含对我们今年和去年会议活动的描述。
因此,让我们开始游戏“查找代码中的错误”。 首先,考虑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块的主体在返回值中包含错别字。 程序员两次错误地指定了
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,左侧是
bool类型的
VertInfluencedByActiveBone()函数的结果,右侧是整数变量
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个高阶位包含单位,但不是一次包含所有位。 换句话说,程序员希望验证高字节中除了0x00和0xFF之外的任何值。 首先,他通过编写表达式(d >> 24)来检查最高有效位是否为非零。 然后,它将高八位移位到低字节。 同时,它计算出最高有效符号位在所有其他位中重复。 也就是说,如果变量d等于0b11111111'00000000'00000000'00000000,那么在移位后,我们将得到值0b11111111'11111111'111111111111111111。 将
int类型的值0xFFFFFFFF加1,程序员计划获得0(-1 + 1 = 0)。 因此,他使用表达式
((d >> 24)+1)来检查不是所有的高八位都等于1。
但是,在移位时,最高位不一定被“抹上”。 标准说:“ E1 >> E2的值是E1右移E2位的位置。 如果E1具有无符号类型,或者E1具有带符号类型且非负值,则结果的值是E1 / 2 / E2的商的整数部分。
如果E1具有带符号的类型和负值,则结果值是实现定义的 。
因此,这是实现定义的行为的示例。 此代码将如何工作取决于微处理器体系结构和编译器的实现。 移位后,最高位很可能出现零,然后表达式
((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编译器的错误而出现错误,是这样吗?
答案该函数返回负值。 原因是编译器不会为按位AND(&)运算符生成代码。 该错误是由于未定义的行为。 编译器发现变量
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)表达式中,可以通过
值 null引用进行访问。 当
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常量传递给
Init方法。
该错误在文章“
荒野西部最快的报告中进行了描述
。此外还有一些错误…… ”
罗斯林错误 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(....)中的空参考
电流进行访问。 执行
nodeOrToken.AsNode()方法的结果是,
当前变量可以获得空值。
来自文章“
检查Roslyn的源代码 ”的错误。
Unity错误 .... staticFields = packedSnapshot.typeDescriptions .Where(t => t.staticFieldBytes != null & t.staticFieldBytes.Length > 0) .Select(t => UnpackStaticFields(t)) .ToArray() ....
答案允许错字:使用
&运算符代替
&&运算符。 这导致以下事实:即使
null变量是
t.staticFieldBytes ,也始终执行检查
t.staticFieldBytes.Length> 0 ,这反过来将导致通过null引用进行访问。
该错误首先在文章“
分析Open 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。仅当所有单词均以大写字母开头时,该函数才返回假值。 在其他情况下,除法将产生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的条件将始终满足,因为变量
计数在循环内不会增加。 假定仅在文件的前四行中搜索xml标记,但是由于错误,将读取整个文件。
与上一个错误类似,该错误已在“
PVS-Studio for Java ”
一文中进行了描述。
仅此而已。 我们正在下次会议上等您。 寻找一个独角兽的立场。 我们将给出新的有趣的难题,当然还有奖品。 待会见!

如果您想与讲英语的读者分享这篇文章,请使用以下链接:Sergey Khrenov。
PVS-Studio团队在2018-2019年会议上提供的Bug发现挑战解决方案 。