pvs-stdio ue4_PVS-Studio团队在2018-2019年会议上提供的Bug发现挑战解决方案

pvs-stdio ue4

Picture 2

Hi! Though the 2019 conference season is not over yet, we'd like to talk about the bug-finding challenges we offered to visitors at our booth during the past conferences. Starting with the fall of 2019, we've been bringing a new set of challenges, so we can now reveal the solutions to the previous tasks of 2018 and the first half of 2019 – after all, many of them came from previously posted articles, and we had a link or QR code with information about the respective articles printed on our challenge leaflets.

嗨! 尽管2019年会议季节尚未结束,但我们想谈一谈过去会议期间我们在展位上为访客提供的Bug查找挑战。 从2019年秋天开始,我们带来了一系列新的挑战,因此我们现在可以揭示针对2018年以前的任务和2019年上半年的解决方案-毕竟,其中许多来自以前发布的文章,并且我们有一个链接或QR码,其中包含有关打印在我们的挑战传单上的各个文章的信息。

If you attended events where we participated with a booth, you probably saw or even tried to solve some of our challenges. These are snippets of code from real open-source projects written in C, C++, C#, or Java. Each snippet contains a bug, and the guests are challenged to try to find it. A successful solution (or simply participation in the discussion of the bug) is rewarded with a prize: a spiral-bound desktop status, a keychain, and the like:

如果您参加了我们通过展位参加的活动,您可能会看到甚至试图解决我们的一些挑战。 这些是使用C,C ++,C#或Java编写的真实开源项目的代码片段。 每个摘录都包含一个错误,并且要求来宾尝试找到它。 成功的解决方案(或只是参与错误的讨论)将获得奖励:螺旋绑定的桌面状态,钥匙串等:

Picture 4

Want some too? Then welcome to drop by our booth at the upcoming events.

也要吗 然后欢迎在即将举行的活动中到我们的展位前来。

By the way, in the articles "Conference Time! Summing up 2018" and "Conferences. Sub-totals for the first half of 2019", we share our experience of participating in the events held earlier this year and in 2018.

顺便说一下,在“ 会议时间!总结2018 ”和“ 会议。2019年上半年小计 ”一文中,我们分享了参加今年年初和2018年举办的活动的经验。

Okay, let's play our «Find the bug» game. First we'll take a look at the earlier challenges of 2018, grouped by language.

好的,让我们玩“发现错误”游戏。 首先,我们将按语言分组分析2018年早期的挑战。

2018年 (2018)

C ++ (C++)

Chrome虫 (Chromium bug)

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];
  }
}

(Solution)

if (time.month == 2 && IsLeapYear(time.year)) {
  return time.month <= kDaysInMonth[time.month] + 1;  // <= day
} else {
  return time.month <= kDaysInMonth[time.month];      // <= day
}

The body of the last If-else block contains typos in the return statements: time.month was accidentally written for a second time instead of time.day. This mistake makes the function return true all the time. The bug is discussed in detail in the article "February 31" and is a cool example of a bug that isn't easily spotted by code review. This case is also a good demonstration of how we use dataflow analysis.

最后一个If-else块的主体在return语句中包含错别字: time.month是第二次而不是time.day意外地写入了。 该错误使函数始终返回true 。 该错误将在文章“ 2月31日 ”中详细讨论,并且是一个很酷的错误示例,该错误很难被代码审查发现。 这个案例也很好地说明了我们如何使用数据流分析。

虚幻引擎错误 (Unreal Engine bug)

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)
  {
  ....
}

(Solution)

VertInfluencedByActiveBone() function has a default value and is not required to be specified. Now look at the VertInfluencedByActiveBone()函数的最后一个参数具有默认值,不需要指定。 现在以简化形式查看 if block in a simplified form: if块:
if (!foo(....) && !foo(....) && !foo(....) & arg)

The bug is now clearly visible. Because of the typo, the third call of the VertInfluencedByActiveBone() function is performed with three arguments instead of four, with the return value then participating in a & operation (bitwise AND: the left operand is the value of type bool returned by VertInfluencedByActiveBone(), and the right operand is the integer variable BoneIndex3). The code is still compilable. This is the fixed version (a comma added, the closing parenthesis moved to the end of the expression):

该错误现在清晰可见。 由于存在拼写错误,对VertInfluencedByActiveBone()函数的第三次调用使用三个参数而不是四个参数执行,返回值然后参与运算(按位AND:左操作数是VertInfluencedByActiveBone()返回的布尔类型的值) ,右边的操作数是整数变量BoneIndex3 )。 该代码仍然可以编译。 这是固定版本(添加逗号,右括号移到表达式的末尾):

if(!VertInfluencedByActiveBone(
      Owner, SourceComponent, VertIndex[0], &BoneIndex1) &&
   !VertInfluencedByActiveBone(
      Owner, SourceComponent, VertIndex[1], &BoneIndex2) && 
   !VertInfluencedByActiveBone(
      Owner, SourceComponent, VertIndex[2], &BoneIndex3))

This error was originally mentioned in the article "A Long-Awaited Check of Unreal Engine 4", where it was titled «the nicest error», which I totally agree with.

该错误最初是在文章“ 对虚幻引擎4的期待已久的检查 ”中提到的,其标题为“最好的错误”,我完全同意。

Android错误 (Android bugs)

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';
    ....
  }
  ....
}

(Solution)

if block. This code doesn't work as expected: if块条件下的操作优先级有错误的假设。 此代码无法正常工作:
if (ssize_t idx = (tagNames.find("3a") != -1))

The idx variable will be assigned the value 0 or 1, and whether the condition is true or false will depend on this value, which is a mistake. This is the fixed version:

将为idx变量分配值0或1,条件是真还是假将取决于此值,这是一个错误。 这是固定版本:

ssize_t idx = tagNames.find("3a");
if (idx != -1)

This bug was mentioned in the article "We Checked the Android Source Code by PVS-Studio, or Nothing is Perfect".

在文章“ 我们通过PVS-Studio检查了Android源代码,或者没有完美的方法 ”中提到了此错误。

Here's another non-trivial challenge with an Android bug:

这是另一个带有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));
}

(Solution)

(d >> 24) + 1 expression. (d >> 24)+ 1表达式中。

The programmer wanted to check that the 8 most significant bits of the d variable are set to 1 but not all of them at a time. In other words, they wanted to check that the most significant byte stores any value except 0x00 and 0xFF. First the programmer checks the most significant bits for null using the (d>>24) expression. Then they shift the eight most significant bits to the least significant byte, expecting the most significant sign bit to get duplicated in all the other bits. That is, if the d variable has the value 0b11111111'00000000'00000000'00000000, it will turn into 0b11111111'11111111'11111111'11111111 after the shift. By adding 1 to the int value 0xFFFFFFFF, the programmer is expecting to get 0 (-1+1=0). Thus, the ((d>>24)+1) expression is used to check that not all of the eight most significant bits are set to 1.

程序员希望检查d变量的8个最高有效位是否设置为1,但不是一次都设置为全部。 换句话说,他们想检查最高有效字节是否存储除0x00和0xFF之外的任何值。 首先,程序员使用(d >> 24)表达式检查最高有效位是否为空。 然后,他们将八个最高有效位移至最低有效字节,并期望最高有效符号位在所有其他位中重复。 也就是说,如果d变量的值为0b11111111'00000000'00000000'00000000,则在移位后它将变为0b11111111'11111111'11111111'11111111。 通过将1与int值0xFFFFFFFF相加,程序员期望得到0(-1 + 1 = 0)。 因此, ((d >> 24)+1)表达式用于检查不是所有八个最高有效位都被设置为1。

However, the most significant sign bit does not necessarily get «spread» when shifted. This is what the standard says: «The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a non-negative value, the value of the result is the integral part of the quotient of E1/2^E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined».

但是,最高有效符号位在移位时不一定会得到“扩展”。 这就是标准所说的:«E1 >> E2的值是E1右移E2位的位置。 如果E1具有无符号类型,或者E1具有带符号类型和非负值,则结果的值是E1 / 2 ^ E2商的整数部分。 如果E1具有带符号的类型和负值,则结果值是实现定义的 »。

So, this is an example of implementation-defined behavior. How exactly this code will work depends on the CPU architecture and compiler implementation. The most significant bits may well end up as zeroes after the shift, and the ((d>>24)+1) expression would then always return a value other than 0, i.e. an always true value.

因此,这是实现定义的行为的示例。 此代码的确切工作方式取决于CPU架构和编译器实现。 移位后,最高有效位很可能最终为零,并且((d >> 24)+1)表达式将始终返回除0以外的值,即始终为真值。

That, indeed, is a non-trivial challenge. Like the previous bug, this one was originally discussed in the article "We Checked the Android Source Code by PVS-Studio, or Nothing is Perfect".

确实,这是一个不平凡的挑战。 像先前的错误一样,该错误最初在文章“ 我们通过PVS-Studio检查了Android源代码,或者Nothing is Perfect ”中进行了讨论。

2019年 (2019)

C ++ (C++)

«这都是海湾合作委员会的错» («It's all GCC's fault»)

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

The programmer blames the GCC 8 compiler for the bug. Is it really GCC's fault?

程序员将此错误归咎于GCC 8编译器。 真的是海湾合作委员会的错吗?

(Solution)

r variable is used to calculate and store a sum, with only positive values involved. The r变量用于计算和存储总和,仅涉及正值。 r variable shouldn't overflow because that would be undefined behavior, which the compiler is not bound to reckon with at all. So it concludes that since r变量不应溢出,因为那将是未定义的行为,编译器根本不会考虑。 因此可以得出结论,由于 r can't have a negative value at the end of the loop, the operation r在循环结束时不能为负值,因此无需执行操作 r & 0x7fffffff, which clears the sign bit, is unnecessary, so it simply tells the function to return the value of r&0x7fffffff来清除符号位,因此只需告诉函数返回 r. r的值即可。

This error was described in the article "PVS-Studio 6.26 Released".

在文章“ PVS-Studio 6.26已发布 ”中描述了此错误。

QT错误 (QT bug)

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

(Solution)

mobj pointer is handled in an unsafe way: first dereferenced, then checked. A classic. mobj指针的处理方式不安全:先取消引用,然后再检查。 经典。

The bug was mentioned in the article "A Third Check of Qt 5 with PVS-Studio".

在“ 使用PVS-Studio对Qt 5进行第三次检查文中提到了该错误。

C# (C#)

Infer.NET错误 (Infer.NET bug)

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}\" ");
}

(Solution)

Null dereference of the value variable may occur when evaluating the value.Equals(defaultValue) expression. This will happen when the variables' values are such that defaultValue != null and value == null.

This bug is from the article "What Errors Lurk in Infer.NET Code?"

评估 value.Equals(defaultValue)表达式时,可能会发生空取消对 value变量的引用。 当变量的值 等于 defaultValue!= nullvalue == null时,就会发生这种情况。

此错误来自文章“ Infer.NET代码中有什么错误潜伏?

FastReport错误 (FastReport bug)

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);

What will the program output in the console? What's wrong with the FastString class?

程序将在控制台中输出什么? FastString类怎么了?

(Solution)

Init method in the constructor: Init方法的变量的拼写错误的名称:
public FastString(int iniCapacity){ Init(initCapacity); }

The constructor parameter iniCapacity won't be used; what gets passed instead is the constant initCapacity.

不会使用构造函数参数iniCapacity ; 相反,传递的是常量initCapacity

The bug was discussed in the article "The Fastest Reports in the Wild West — and a Handful of Bugs..."

该错误已在“ 荒野西部最快的报告-以及少数错误...文中进行了讨论

罗斯林错误 (Roslyn bug)

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

(Solution)

current in the current.FullSpan.Contains(....)表达式中 current.FullSpan.Contains(....) expression. The 当前的潜在空引用。 调用 current variable can be assigned a null value as a result of invoking the nodeOrToken.AsNode()方法的结果可以为 nodeOrToken.AsNode() method. 当前变量分配一个空值。

This bug is from the article "Checking the Roslyn Source Code".

此错误来自文章“ 检查Roslyn源代码 ”。

Unity错误 (Unity bug)

....
staticFields = packedSnapshot.typeDescriptions
               .Where(t => 
                      t.staticFieldBytes != null & 
                      t.staticFieldBytes.Length > 0)
               .Select(t => UnpackStaticFields(t))
               .ToArray()
....

(Solution)

A typo: the & operator is used instead of &&. This results in executing the t.staticFieldBytes.Length > 0 check all the time, even if the t.staticFieldBytes variable is null, which, in its turn, leads to a null dereference.

This bug was originally shown in the article "Discussing Errors in Unity3D's Open-Source Components".

错字:使用 运算符代替 && 。 即使 t.staticFieldBytes变量为 null ,这也会导致 始终执行 t.staticFieldBytes.Length> 0检查,这反过来又导致空取消引用。

此错误最初在文章“ 讨论Unity3D的开源组件中的错误 ”中显示。

Java (Java)

IntelliJ IDEA错误 (IntelliJ IDEA bug)

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
}

Why does the program incorrectly calculate the number of capitalized words?

为什么程序会错误地计算大写单词的数量?

(Solution)

true if the number of capitalized words is less than 20%. But the check doesn't work because of the integer division, which evaluates only to 0 or 1. The function will return true 。 但是由于整数除法,该检查不起作用,整数除法仅求值为0或1。仅当所有单词都大写时,该函数才会返回 false only if all the words are capitalized. Otherwise, the division will result in 0 and the function will return false 。 否则,除法将得出0,并且该函数将返回 true. true

This bug is from the article "PVS-Studio for Java".

该错误来自文章“ PVS-Studio for Java ”。

SpotBugs错误 (SpotBugs bug)

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");
  ....
}

What's wrong with the search of the xml tag?

搜索xml标记有什么问题?

(Solution)

count < 4 condition will be always true since the variable 计数<4的条件将始终为true,因为变量 count is not incremented inside the loop. The xml tag was meant to be searched for in the first four lines of the file, but because of the lacking increment, the program will be reading the entire file. 计数在循环内不会增加。 xml标记本应在文件的前四行中搜索,但由于缺少增量,因此程序将读取整个文件。

Like the previous bug, this one was described in the article "PVS-Studio for Java".

像以前的错误一样,该错误在文章“ PVS-Studio for Java ”中进行了描述。

That's all for today. Come see us at the upcoming events – look for the unicorn. We'll be offering new interesting challenges and, of course, giving prizes. See you!

今天就这些。 快来看我们即将举行的活动–寻找独角兽。 我们将提供新的有趣挑战,当然还有奖品。 再见!

翻译自: https://habr.com/en/company/pvs-studio/blog/476268/

pvs-stdio ue4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值