﻿# Into Space Again: how the Unicorn Visited Stellarium

Over the course of its history, humanity has been making enormous efforts to study the night sky\. By now, we have mapped almost the entire area of it\. We have observed hundreds of thousands of asteroids, comets, planets and stars, nebulas and galaxies\. To see all these wonders yourself, you don't even have to leave home and buy a telescope \- you can simply install Stellarium, a virtual planetarium, on your computer and explore the night sky while stretching out comfortably on your sofa\.\.\. But is it that comfortable? Let's check the code of Stellarium for bugs to find it out\.

![0597_Stellaruim/image1.png](https://import.viva64.com/docx/blog/0597_Stellaruim/image1.png)

## A few words about the project\.\.\.

According to the [Wikipedia page](https://en.wikipedia.org/wiki/Stellarium_(software)), [Stellarium](https://stellarium.org/) is an open\-source free\-software planetarium, licensed under the terms of the GNU General Public License version 2, available for Linux, Windows, and macOS\. A port Stellarium called Stellarium Mobile is available for Android, iOS, and Symbian as a paid version, being developed by Noctua Software\. All versions use OpenGL to render a realistic projection of the night sky in real time\.

Stellarium was created by the French programmer Fabien Chéreau, who launched the project in the summer of 2001 \(17 years ago\)\. Currently, Stellarium is being maintained and developed by Alexander Wolf, Georg Zotti, Marcos Cardinot, Guillaume Chéreau, Bogdan Marinov, Timothy Reaves, Ferdinand Majerech, and Jörg Müller\. A number of other developers have contributed to the development of Stellarium, especially Robert Spearman, Johannes Gajdosik, Matthew Gates, Nigel Kerr, and Johan Meuris, the latter of whom is responsible for the artwork\.

## \.\.\.and the analyzer

The project was analyzed with the static code analyzer PVS\-Studio\. This is a tool to detect bugs and potential vulnerabilities in programs written in C, C\+\+, and C\# \(Java support is coming soon\!\)\. It supports Windows, Linux, and macOS and is designed for developers who care about improving the code's quality\. 

It was quite simple to do the analysis\. First, I [downloaded](https://github.com/Stellarium) Stellarium's source code from GitHub and then installed all software packages required to build the project\. Since it is built with Qt Creator, I used the compiler\-launch tracking mechanism, a special feature of the [Standalone](https://pvs-studio.com/en/docs/manual/0033/) version of PVS\-Studio\. It can open analysis reports as well\.

New readers and Stellarium users are probably wondering why I mentioned a unicorn in the title and what it has to do with code analysis\. The answer is, I am one of the developers of PVS\-Studio, and the unicorn is our dear playful mascot\. Now, up we go\! 

![0597_Stellaruim/image2.png](https://import.viva64.com/docx/blog/0597_Stellaruim/image2.png)

Figure 1\. Going up\!

I hope you will learn something new from this article, while the authors of Stellarium will fix some of the bugs and, therefore, make the project better\. 

Get yourself a coffee and a croissant and sit back: we are getting to the most interesting part of our articles \- overview of the bugs reported by the analyzer\!

## Suspicious conditions

To make it more entertaining, I recommend that in every case \(starting with this one\) you try to find the bug yourself first and only then read the analyzer warning and my comments:

```cpp
void QZipReaderPrivate::scanFiles()
{
  ....
  // find EndOfDirectory header
  int i = 0;
  int start_of_directory = -1;
  EndOfDirectory eod;
  while (start_of_directory == -1) {
    const int pos = device->size() 
      - int(sizeof(EndOfDirectory)) - i;
    if (pos < 0 || i > 65535) {
      qWarning() << "QZip: EndOfDirectory not found";
      return;
    }

    device->seek(pos);
    device->read((char *)&eod, sizeof(EndOfDirectory));
    if (readUInt(eod.signature) == 0x06054b50)
      break;
    ++i;
  }
  ....
}
```

**PVS\-Studio diagnostic message:** [V654](https://pvs-studio.com/en/docs/warnings/v654/) The condition 'start\_of\_directory \=\= \- 1' of loop is always true\. qzip\.cpp 617

Found it? If yes, kudos to you\!

The problem is in the condition of the _while_ loop\. This condition is always true as the _start\_of\_directory_ variable does not change inside the loop body\. It doesn't look like the loop is going to run forever because it has a _return_ and _break_ in it, but it still looks suspicious\.

I think that the programmer forgot to add the assignment _start\_of\_directory \= pos_ in the signature check\. If so, the _break_ statement is not necessary either\. The code could be rewritten as follows:

```cpp
int i = 0;
int start_of_directory = -1;
EndOfDirectory eod;
while (start_of_directory == -1) {
  const int pos = device->size() 
    - int(sizeof(EndOfDirectory)) - i;
  if (pos < 0 || i > 65535) {
    qWarning() << "QZip: EndOfDirectory not found";
    return;
  }

  device->seek(pos);
  device->read((char *)&eod, sizeof(EndOfDirectory));
  if (readUInt(eod.signature) == 0x06054b50)
    start_of_directory = pos;
  ++i;
}
```

However, I'm not sure this is exactly what it is supposed to look like\. The authors should check this part for themselves and make the necessary improvements\.

Here's another strange condition:

```cpp
class StelProjectorCylinder : public StelProjector
{
public:
  ....
protected:
  ....
  virtual bool 
  intersectViewportDiscontinuityInternal(const Vec3d& capN, 
                                         double capD) const
  {
    static const SphericalCap cap1(1,0,0);
    static const SphericalCap cap2(-1,0,0);
    static const SphericalCap cap3(0,0,-1);
    SphericalCap cap(capN, capD);
    return cap.intersects(cap1) 
        && cap.intersects(cap2) 
        && cap.intersects(cap2);
  }
};
```

**PVS\-Studio diagnostic message:** [V501](https://pvs-studio.com/en/docs/warnings/v501/) There are identical sub\-expressions 'cap\.intersects\(cap2\)' to the left and to the right of the '&&' operator\. StelProjectorClasses\.hpp 175

As you have probably guessed already, the bug is in the last line of the function: the programmer made a typo that makes the function ignore the actual value of _cap3_ when returning\.

This error pattern is extremely common: nearly every project we checked had typos in variable names of the form _name1_, _name2_, and the like\. This usually happens when using the copy\-paste technique\.

The snippet above is an example of another common error pattern, which we even did a little research on\. My colleague Andrey Karpov called it "[the last line effect](https://pvs-studio.com/en/blog/posts/cpp/0260/)"\. If you haven't heard about it yet, I do recommend opening the article in a new tab of your browser to read later\. Let's move on\.

```cpp
void BottomStelBar::updateText(bool updatePos)
{
  ....
  updatePos = true;
  ....
  if (location->text() != newLocation || updatePos)
  {
    updatePos = true;
    ....
  }
  ....
  if (fov->text() != str)
  {
    updatePos = true;
    ....
  }
  ....
  if (fps->text() != str)

  {
    updatePos = true;
    ....
  }

  if (updatePos)
  {
    ....
  }
}
```

**PVS\-Studio diagnostic messages:**

* [V560](https://pvs-studio.com/en/docs/warnings/v560/) A part of conditional expression is always true: updatePos\. StelGuiItems\.cpp 732
* [V547](https://pvs-studio.com/en/docs/warnings/v547/) Expression 'updatePos' is always true\. StelGuiItems\.cpp 831 
* [V763](https://pvs-studio.com/en/docs/warnings/v763/) Parameter 'updatePos' is always rewritten in function body before being used\. StelGuiItems\.cpp 690

The value of the _updatePos_ parameter is always overwritten before it can be used\. That is, the function will always return with the same result no matter what value is passed to it\.

It doesn't look right, does it? Whenever the _updatePos_ parameter is used, it has the value _true_, which means the conditions _if \(location\-\>text\(\) \!\= newLocation \|\| updatePos\)_ and _if \(updatePos\)_ will be always true\.

Another snippet:

```cpp
void LandscapeMgr::onTargetLocationChanged(StelLocation loc)
{
  ....
  if (pl && flagEnvironmentAutoEnabling)
  {
    QSettings* conf = StelApp::getInstance().getSettings();
    setFlagAtmosphere(pl->hasAtmosphere() 
                    & conf->value("landscape/flag_atmosphere", true).toBool());
    setFlagFog(pl->hasAtmosphere() 
             & conf->value("landscape/flag_fog", true).toBool());
    setFlagLandscape(true);
  }
  ....
}
```

**PVS\-Studio diagnostic messages:** 

* [V792](https://pvs-studio.com/en/docs/warnings/v792/) The 'toBool' function located to the right of the operator '&' will be called regardless of the value of the left operand\. Perhaps, it is better to use '&&'\. LandscapeMgr\.cpp 782
* [V792](https://pvs-studio.com/en/docs/warnings/v792/) The 'toBool' function located to the right of the operator '&' will be called regardless of the value of the left operand\. Perhaps, it is better to use '&&'\. LandscapeMgr\.cpp 783

The analyzer has detected a suspicious expression in the arguments of the functions _setFlagAtmosphere_ and _setFlagFog_\. Indeed, both operands of the bitwise operator _&_ are values of type _bool_\. What should be used instead of _&_ is the _&&_ operator, and here's why\. 

True, the result of that expression will always be correct\. Before the bitwise AND is executed, both operands will be promoted to type _int_\. In C\+\+, such cast is [unambiguous](http://eelis.net/c++draft/conv.prom): false converts into 0 and true converts into 1\. That's why it evaluates to the same result as it would with the _&&_ operator\. 

However, there's one subtle yet important difference\. For _&&_ operations, the so called lazy evaluation is used\. If the left operand's value is _false_, then the right operand is not evaluated at all because the logical AND will evaluate to _false_ anyway\. This is done for the sake of saving computational resources and allowing programmers to write complex structures\. For example, you can check a pointer for null and, if it's found to be non\-null, dereference it to do an additional check, like this: _if \(ptr && ptr\-\>foo\(\)\)_\.

This lazy evaluation strategy is not applied to operations with the bitwise _&_\. The expressions _conf\-\>value\("\.\.\.", true\)\.toBool\(\)_ will be evaluated every time no matter the value of _pl\-\>hasAtmosphere\(\)_\.

In rare cases, this can be a deliberate trick used, for instance, when evaluation of the right operand has certain "side effects" which the programmer wants to save for later use\. That's not a good thing to do either because it makes the code harder to read and maintain\. Besides, the evaluation order of the operands in the _&_ operation is not defined, so using such "tricks" may end up with undefined behavior\. 

If you need to save the side effects, do that in a separate line and store the result to some separate variable\. Those who will be maintaining the code later will be thankful for that :\)

![0597_Stellaruim/image3.png](https://import.viva64.com/docx/blog/0597_Stellaruim/image3.png)

Figure 2\. Peering into the night sky\.

Moving on to the next section\.

## Incorrect memory management

This section is about managing dynamic memory, and we'll start with the following snippet:

```cpp
/************ Basic Edge Operations ****************/
/* __gl_meshMakeEdge creates one edge,
 * two vertices, and a loop (face).
 * The loop consists of the two new half-edges.
 */
GLUEShalfEdge* __gl_meshMakeEdge(GLUESmesh* mesh)
{
  GLUESvertex* newVertex1 = allocVertex();
  GLUESvertex* newVertex2 = allocVertex();
  GLUESface* newFace = allocFace();
  GLUEShalfEdge* e;
  
  /* if any one is null then all get freed */
  if ( newVertex1 == NULL 
    || newVertex2 == NULL 
    || newFace == NULL)
  {
    if (newVertex1 != NULL)
    {
      memFree(newVertex1);
    }
    if (newVertex2 != NULL)
    {
      memFree(newVertex2);
    }
    if (newFace != NULL)
    {
      memFree(newFace);
    }
    return NULL;
  }
  
  e = MakeEdge(&mesh->eHead);
  if (e == NULL)
  {
    return NULL;
  }
  
  MakeVertex(newVertex1, e, &mesh->vHead);
  MakeVertex(newVertex2, e->Sym, &mesh->vHead);
  MakeFace(newFace, e, &mesh->fHead);
  
  return e;
}
```

**PVS\-Studio diagnostic messages:** 

* [V773](https://pvs-studio.com/en/docs/warnings/v773/) The function was exited without releasing the 'newVertex1' pointer\. A memory leak is possible\. mesh\.c 312
* [V773](https://pvs-studio.com/en/docs/warnings/v773/) The function was exited without releasing the 'newVertex2' pointer\. A memory leak is possible\. mesh\.c 312
* [V773](https://pvs-studio.com/en/docs/warnings/v773/) The function was exited without releasing the 'newFace' pointer\. A memory leak is possible\. mesh\.c 312

The function allocates memory for three structures and passes it to the pointers _newVertex1_, _newVertex2_ \(remember what I told you about variable names?\), and _newFace_\. If one of them turns out to be null, all memory reserved in the function is freed and the function returns NULL\. 

But what if memory is successfully allocated for all three structures but the _MakeEdge\(&mesh\-\>eHead\)_ function returns _NULL_? In that case, execution will reach the second _return_ statement\.

Since the pointers _newVertex1_, _newVertex2_, and _newFace_ are local variables, they will cease to exist after the function returns\. However, the memory that was previously allocated for them will not be freed\. It will remain reserved, yet you will no longer be able to access it\. 

Such defects are called "memory leaks"\. The typical scenario involving them is this: when running for a long time, the program starts to consume more and more memory and may even use up all of it\. 

Note that the third _return_ is OK in this example\. The functions _MakeVertex_ and _MakeFace_ pass the allocated addresses to other data structures, thus delegating to them the responsibility for releasing that memory\.

The next defect was found in a method more than 90 lines long\. I abridged it for you and kept only the flawed lines\.

```cpp
void AstroCalcDialog::drawAngularDistanceGraph()
{
  ....
  QVector<double> xs, ys;
  ....
}
```

Only one line is left\. Hint: this is the only time the objects _xs_ and _ys_ are mentioned\.

**PVS\-Studio diagnostic messages:** 

* [V808](https://pvs-studio.com/en/docs/warnings/v808/) 'xs' object of 'QVector' type was created but was not utilized\. AstroCalcDialog\.cpp 5329
* [V808](https://pvs-studio.com/en/docs/warnings/v808/) 'ys' object of 'QVector' type was created but was not utilized\. AstroCalcDialog\.cpp 5329

The vectors _xs_ and _ys_ are created but never used\. It turns out that every time the _drawAngularDistanceGraph_ method is called, an empty container is created and deleted, which is totally redundant\. I think this declaration is a trace of previous refactoring\. It's not an error, of course, but it's still better to get rid of redundant code\.



## Strange type conversions

Here's one more example with a little editing by me:

```cpp
void SatellitesDialog::updateSatelliteData()
{
  ....
  // set default
  buttonColor = QColor(0.4, 0.4, 0.4);
  ....
}
```

To find the defect, you'll have to look at the prototypes of the constructors of [the Qcolor class](https://doc.qt.io/archives/qt-4.8/qcolor.html):

![0597_Stellaruim/image4.png](https://import.viva64.com/docx/blog/0597_Stellaruim/image4.png)

**PVS\-Studio diagnostic messages:**

* [V674](https://pvs-studio.com/en/docs/warnings/v674/) The literal '0\.4' of 'double' type is being implicitly cast to 'int' type while calling the 'QColor' function\. Inspect the first argument\. SatellitesDialog\.cpp 413
* [V674](https://pvs-studio.com/en/docs/warnings/v674/) The literal '0\.4' of 'double' type is being implicitly cast to 'int' type while calling the 'QColor' function\. Inspect the second argument\. SatellitesDialog\.cpp 413
* [V674](https://pvs-studio.com/en/docs/warnings/v674/) The literal '0\.4' of 'double' type is being implicitly cast to 'int' type while calling the 'QColor' function\. Inspect the third argument\. SatellitesDialog\.cpp 413

The _Qcolor_ class doesn't have any constructors taking a value of type _double_ as an argument, so the arguments will be implicitly cast to _int_\. As a result, the fields _r_, _g_, _b_ of the _buttonColor_ object will all have the value _0_\.

If the programmer wanted to form an object from values of type _double_, they should have used a different constructor\.

For example, it could be a constructor taking _Qrgb_ as an argument:

```cpp
buttonColor = QColor(QColor::fromRgbF(0.4, 0.4, 0.4));
```

There's also another way to do that\. In Qt, RGB\-colors are represented by real numbers from the range \[0\.0, 1\.0\] or integers from the range \[0, 255\]\.

So, the programmer could cast the real numbers to integers as follows:

```cpp
buttonColor = QColor((int)(255 * 0.4), 
                     (int)(255 * 0.4), 
                     (int)(255 * 0.4));
```

or simply:

```cpp
buttonColor = QColor(102, 102, 102);
```

Starting to get bored? Don't worry: there are more interesting things ahead\.

![0597_Stellaruim/image5.png](https://import.viva64.com/docx/blog/0597_Stellaruim/image5.png)

Figure 3\. Unicorn in outer space\. View from Stellarium\. Click on the image to enlarge\.

## Other errors

I kept a few cool examples for this last section :\) Here's one of them\.

```cpp
HipsTile* HipsSurvey::getTile(int order, int pix)
{
  ....
  if (order == orderMin && !allsky.isNull())
  {
    int nbw = sqrt(12 * 1 << (2 * order));
    int x = (pix % nbw) * allsky.width() / nbw;
    int y = (pix / nbw) * allsky.width() / nbw;
    int s = allsky.width() / nbw;
    QImage image = allsky.copy(x, y, s, s);
    ....
  }
  ....
}
```

**PVS\-Studio diagnostic message:** [V634](https://pvs-studio.com/en/docs/warnings/v634/) The priority of the '\*' operation is higher than that of the '<<' operation\. It's possible that parentheses should be used in the expression\. StelHips\.cpp 271

What about this one? Found it? Let's examine the _\(12_ _\*_ _1_ _<<_ _\(2_ _\*_ _order\)\)_ expression\. The analyzer reminds us that the '_\*_' operation has higher precedence than the bit shifting operation '_<<_' does\. It's easy to see that multiplying _12_ by _1_ makes no sense and there's no need to enclose _2_ _\*_ _order_ in parentheses\. 

What the programmer must have really meant is this:

```cpp
int nbw = sqrt(12 * (1 << 2 * order));
```

Now the value _12_ is multiplied by the correct number\.

Note\. There's one more thing I'd like to point out: if the value of the right operand of '_<<_' is greater than or equal to the number of bits of the left operand, the result is not defined\. Since numeric literals are by default of type _int_, which is _32_ bits long, the value of the _order_ parameter must not exceed _15_\. Otherwise, the program may end up with undefined behavior\.

Moving on\. The code below is quite intricate, but I'm sure you are skilled enough to spot the bug :\) 

```cpp
/* inherits documentation from base class */
QCPRange QCPStatisticalBox::
getKeyRange(bool& foundRange, SignDomain inSignDomain) const
{
  foundRange = true;
  if (inSignDomain == sdBoth)
  {
    return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
  }
  else if (inSignDomain == sdNegative)
  {
    if (mKey + mWidth * 0.5 < 0)
      return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
    else if (mKey < 0)
      return QCPRange(mKey - mWidth * 0.5, mKey);
    else
    {
      foundRange = false;
      return QCPRange();
    }
  }
  else if (inSignDomain == sdPositive)
  {
    if (mKey - mWidth * 0.5 > 0)
      return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
    else if (mKey > 0)
      return QCPRange(mKey, mKey + mWidth * 0.5);
    else
    {
      foundRange = false;
      return QCPRange();
    }
  }
  foundRange = false;
  return QCPRange();
}
```

**PVS\-Studio diagnostic message:** [V779](https://pvs-studio.com/en/docs/warnings/v779/) Unreachable code detected\. It is possible that an error is present\. qcustomplot\.cpp 19512\.

The point is that each _if\.\.\.else_ branch has a _return_ statement \- that's why execution will never reach the last two lines\.

Technically speaking, this code will execute correctly\. It's just that the presence of unreachable code is in itself a signal of some problem\. In this case, it indicates that the method is not structured properly, which makes the code much harder to understand\.

This function needs refactoring to make it neater\. For example:

```cpp
/* inherits documentation from base class */
QCPRange QCPStatisticalBox::
getKeyRange(bool& foundRange, SignDomain inSignDomain) const
{
  foundRange = true;

  switch (inSignDomain)
  {
  case sdBoth:
  {
    return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
    break;
  }
  case sdNegative:
  {
    if (mKey + mWidth * 0.5 < 0)
      return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
    else if (mKey < 0)
      return QCPRange(mKey - mWidth * 0.5, mKey);
    break;
  }
  case sdPositive: {
    if (mKey - mWidth * 0.5 > 0)
      return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
    else if (mKey > 0)
      return QCPRange(mKey, mKey + mWidth * 0.5);
    break;
  }
  }

  foundRange = false;
  return QCPRange();
}
```

The last bug is my favorite one in this project\. The snippet in question is short and straightforward:

```cpp
Plane::Plane(Vec3f &v1, Vec3f &v2, Vec3f &v3)
  : distance(0.0f), sDistance(0.0f)
{
  Plane(v1, v2, v3, SPolygon::CCW);
}
```

Noticed anything odd? Not everyone can :\)

**PVS\-Studio diagnostic message:** [V603](https://pvs-studio.com/en/docs/warnings/v603/) The object was created but it is not being used\. If you wish to call constructor, 'this\-\>Plane::Plane\(\.\.\.\.\)' should be used\. Plane\.cpp 29

The programmer relied on several of the object's fields to be initialized in the nested constructor, but what happens instead is this\. When calling the _Plane\(Vec3f &v1, Vec3f &v2, Vec3f &v3\)_ constructor, an unnamed temporary object is created and deleted right away inside it, while the fields remain uninitialized\. 

To make the code work properly, the developers should use a safe and handy feature of C\+\+11 \- a delegating constructor:

```cpp
Plane::Plane(Vec3f& v1, Vec3f& v2, Vec3f& v3)
  : Plane(v1, v2, v3, SPolygon::CCW)
{
  distance = 0.0f;
  sDistance = 0.0f;
}
```

But if your compiler doesn't support the new language version, you may write it like this:

```cpp
Plane::Plane(Vec3f &v1, Vec3f &v2, Vec3f &v3)
  : distance(0.0f), sDistance(0.0f)
{
  this->Plane::Plane(v1, v2, v3, SPolygon::CCW);
}
```

Or like this:

```cpp
Plane::Plane(Vec3f &v1, Vec3f &v2, Vec3f &v3)
  : distance(0.0f), sDistance(0.0f)
{
  new (this) Plane(v1, v2, v3, SPolygon::CCW);
}
```

Note that the last two solutions are [pretty dangerous](https://pvs-studio.com/en/docs/warnings/v603/)\. Be careful and make sure you understand how exactly they work\.

## Conclusion

So, what can I say about the quality of Stellarium's code? To be honest, there weren't many bugs\. What's more, I haven't found a single error that depends on undefined behavior\. For an open\-source project, the code is very high\-quality, and I take my hat off to them\. Good job, guys\! It was a pleasure reviewing your project\.

As for the planetarium itself, I do use it quite often\. Sadly, because I live in a city, I rarely get an opportunity to marvel at clear sky at night, but Stellarium can take me to any spot on our planet without my bothering to stand up from the sofa\. So, yes, it's comfortable indeed\! 

I especially like the "Constellation art" mode\. It's really breathtaking to watch huge figures float across the sky in a mysterious dance\!

![0597_Stellaruim/image6.png](https://import.viva64.com/docx/blog/0597_Stellaruim/image6.png)

Figure 4\. Mysterious dance\. View from Stellarium\. Click on the image to enlarge\.

We Earth dwellers tend to make mistakes, and there's nothing shameful about overlooking some bugs in programs\. For this, code analysis tools such as PVS\-Studio are being developed\. If you, too, live on Earth, welcome to [download and try PVS\-Studio](https://pvs-studio.com/en/pvs-studio/download/)\.

I hope you enjoyed reading this article and learned something cool and useful\. And I also hope that the authors of Stellarium will get the bugs fixed soon\. I wish them good luck with that\!

Subscribe to our channels to follow the news of the programming world\!

* Fb: [@StaticCodeAnalyzer](https://www.facebook.com/StaticCodeAnalyzer/)
* Telegram: [@pvsstudio\_en](https://t.me/pvsstudio_en)
* Twitter: [@Code\_Analysis](https://twitter.com/Code_Analysis)
* YouTube: [@PVSStudioTool](https://www.youtube.com/c/PVSStudioTool)