在2018-2019年会议的PVS-Studio展位上回答任务

图片2


你好 尽管2019年会议季节仍在进行中,但我们想讨论一下早些时候提供给我们展位访客的任务。 我们在2019年秋季开始时使用了一组新任务,因此已经可以发布2018年以及2019年上半年的旧问题的解决方案。此外,其中许多是从以前发表的文章中摘取的,任务传单中包含链接或QR码有关文章的信息。

如果您参加了我们在展台旁站立的会议,您可能会看到甚至解决我们的一些问题。 这些总是来自真正的开源项目的C,C ++,C#或Java编程语言的代码片段。 该代码包含我们建议访问者查找的错误。 对于解决方案(或仅是错误的讨论),我们提供奖品-桌面上的状态,小装饰品等:

图片4

你想要一样吗? 在下届会议上来到我们的展位。

顺便说一下,文章“ 会议时间!总结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; // <= day } else { return time.month <= kDaysInMonth[time.month]; // <= day } 

最后一个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); // Expand shorthands if (ssize_t idx = tagNames.find("3a") != -1) { ssize_t end = tagNames.find(",", idx); char* start = tagNames.lockBuffer(tagNames.size()); start[idx] = '\0'; .... } .... } 

答案
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; } 

答案
mobj指针不安全。 首先,将其取消引用,然后再进行检查。 经典版

该错误在文章“ 使用PVS-Studio进行第三次Qt 5测试 ”中进行了描述。

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!= Nullvalue == 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; // allow reasonable amount of // capitalized words } 

建议确定为什么不正确计算带有大写字母的单词数。

答案
如果少于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发现挑战解决方案

Source: https://habr.com/ru/post/zh-CN476272/


All Articles