Celestia:太空虫的冒险

图片1

Celestia是一个三维空间模拟器。 空间模拟使我们可以从三个维度探索宇宙。 Celestia在Windows,Linux和macOS上可用。 该项目非常小,使用PVS-Studio可以检测到非常少的缺陷。 但是,我们真的要引起他的注意,因为这是一个受欢迎的教育项目,对改进他很有用。 顺便说一下,该程序用于流行的电影,系列和代表空间的程序中。 这也提高了代码的质量要求。

引言


Celestia项目的官方网站上,您可以找到它的详细描述。 该项目的源代码托管在GitHub上 。 除库和测试外,分析器还检查了166个.cpp文件。 这个项目很小,但是发现的缺陷却很有趣。

为了分析代码,我们使用了PVS-Studio静态代码分析器。 Celestia和PVS-Studio都是跨平台的。 为了进行分析,选择了Windows平台。 通过使用Microsoft库管理器Vcpkg提取依赖关系,可以轻松构建该项目。 根据评论,它不如Conan的功能,但该程序使用起来也很方便。

分析结果


警告1

V501 '<'运算符的左侧和右侧有相同的子表达式:b.nAttributes <b.nAttributes cmodfix.cpp 378

bool operator<(const Mesh::VertexDescription& a, const Mesh::VertexDescription& b) { if (a.stride < b.stride) return true; if (b.stride < a.stride) return false; if (a.nAttributes < b.nAttributes) // <= return true; if (b.nAttributes < b.nAttributes) // <= return false; for (uint32_t i = 0; i < a.nAttributes; i++) { if (a.attributes[i] < b.attributes[i]) return true; else if (b.attributes[i] < a.attributes[i]) return false; } return false; } 

复制代码时容易出错。 我们在每次评论中都对此进行介绍。 显然,在这种情况下,只有静态代码分析才能提供帮助。

程序员复制了条件表达式,但没有完全对其进行编辑。 正确的选项很可能是这样的:

 if (a.nAttributes < b.nAttributes) return true; if (b.nAttributes < a.nAttributes) return false; 

关于这个主题的有趣研究:“ 邪恶生活在比较功能中”

警告2

V575'memset '函数处理'0'元素。 检查第三个论点。 winmain.cpp 2235

 static void BuildScriptsMenu(HMENU menuBar, const fs::path& scriptsDir) { .... MENUITEMINFO info; memset(&info, sizeof(info), 0); info.cbSize = sizeof(info); info.fMask = MIIM_SUBMENU; .... } 

该代码的作者将第二个参数和第三个参数混入了memset函数。 而不是用零填充结构,而是指示要填充0字节的内存。

警告3

V595在针对nullptr进行验证之前,已使用“目标”指针。 检查行:48,50。wintourguide.cpp 48

 BOOL APIENTRY TourGuideProc(....) { .... const DestinationList* destinations = guide->appCore->getDestinations(); Destination* dest = (*destinations)[0]; guide->selectedDest = dest; if (hwnd != NULL && destinations != NULL) { .... } .... } 

目标指针解引用的高度比与NULL相比要高两行。 这样的代码可能会导致错误。

警告4

V702类应始终从std :: exception(类似)作为“ public”派生(未指定关键字,因此编译器默认将其设置为“ private”)。 fs.h 21

 class filesystem_error : std::system_error { public: filesystem_error(std::error_code ec, const char* msg) : std::system_error(ec, msg) { } }; // filesystem_error 

分析器通过private修饰符(默认指定)检测到从std :: exception类继承的类。 此继承很危险,因为在非公共继承的情况下,当尝试捕获std :: exception异常时 ,它将被跳过。 结果,异常处理程序的行为不符合预期。

警告5

V713在针对同一逻辑表达式中的nullptr进行验证之前,在逻辑表达式中使用了指针“ s”。 winmain.cpp 3031

 static char* skipUntilQuote(char* s) { while (*s != '"' && s != '\0') s++; return s; } 

在条件表达式的一处,他们忘记了取消对指针s的引用。 结果是指针的比较,而不是指针上的值。 在这种情况下,这是没有意义的。

警告6

V773在不释放'vertexShader'指针的情况下退出了该函数。 可能发生内存泄漏。 modelviewwidget.cpp 1517

 GLShaderProgram* ModelViewWidget::createShader(const ShaderKey& shaderKey) { .... auto* glShader = new GLShaderProgram(); auto* vertexShader = new GLVertexShader(); if (!vertexShader->compile(vertexShaderSource.toStdString())) { qWarning("Vertex shader error: %s", vertexShader->log().c_str()); std::cerr << vertexShaderSource.toStdString() << std::endl; delete glShader; return nullptr; } .... } 

退出函数时, glShader指针会释放内存,但vertexShader指针不会清除内存

同一位置的代码较低:

  • V773在不释放'fragmentShader'指针的情况下退出了该函数。 可能发生内存泄漏。 modelviewwidget.cpp 1526

警告7

V547表达式'!InputFilename.empty()'始终为true。 makexindex.cpp 128

 int main(int argc, char* argv[]) { if (!parseCommandLine(argc, argv) || inputFilename.empty()) { Usage(); return 1; } istream* inputFile = &cin; if (!inputFilename.empty()) { inputFile = new ifstream(inputFilename, ios::in); if (!inputFile->good()) { cerr << "Error opening input file " << inputFilename << '\n'; return 1; } } .... } 

重新检查文件名。 这不是错误,但是由于在函数开始时已经检查inputFilename变量,因此可以在下面删除验证,这将使代码更紧凑。

警告8

V556比较不同枚举类型的值:开关(ENUM_TYPE_A){情况ENUM_TYPE_B:...}。 render.cpp 7457

 enum LabelAlignment { AlignCenter, AlignLeft, AlignRight }; enum LabelVerticalAlignment { VerticalAlignCenter, VerticalAlignBottom, VerticalAlignTop, }; struct Annotation { .... LabelVerticalAlignment valign : 3; .... }; void Renderer::renderAnnotations(....) { .... switch (annotations[i].valign) { case AlignCenter: vOffset = -font[fs]->getHeight() / 2; break; case VerticalAlignTop: vOffset = -font[fs]->getHeight(); break; case VerticalAlignBottom: vOffset = 0; break; } .... } 

switch语句中 ,枚举值被混淆。 因此,在一个地方比较了不同类型的枚举: LabelVerticalAlignmentAlignCenter

警告9

V581彼此并排放置的'if'语句的条件表达式相同。 检查线:2844、2850。shadermanager.cpp 2850

 GLVertexShader* ShaderManager::buildParticleVertexShader(const ShaderProperties& props) { .... if (props.texUsage & ShaderProperties::PointSprite) { source << "uniform float pointScale;\n"; source << "attribute float pointSize;\n"; } if (props.texUsage & ShaderProperties::PointSprite) { source << DeclareVarying("pointFade", Shader_Float); } .... } 

分析器连续检测到两个相同的条件表达式。 有一个错误,或者可以将两个条件组合为一个,从而简化了代码。

警告10

V668测试'dp'指针是否为null没有意义,因为内存是使用'new'运算符分配的。 如果内存分配错误,将生成异常。 windatepicker.cpp 625

 static LRESULT DatePickerCreate(HWND hwnd, CREATESTRUCT& cs) { DatePicker* dp = new DatePicker(hwnd, cs); if (dp == NULL) return -1; .... } 

运算符返回的指针值将与零进行比较。 如果操作员无法分配内存,则根据C ++语言标准,将生成异常std :: bad_alloc() 。 因此,检查指向零的指针是没有意义的。

另外三项类似的检查:

  • V668测试“ modes”指针是否为null没有意义,因为使用“ new”运算符分配了内存。 如果内存分配错误,将生成异常。 winmain.cpp 2967
  • V668没有针对空测试'dropTarget'指针的意义,因为使用'new'运算符分配了内存。 如果内存分配错误,将生成异常。 winmain.cpp 3272
  • V668测试“ appCore”指针是否为null没有意义,因为使用“ new”运算符分配了内存。 如果内存分配错误,将生成异常。 winmain.cpp 3352

警告11

V624正在使用常数3.14159265。 结果值可能不正确。 考虑使用<math.h>中的M_PI常量。 3dstocmod.cpp 62

 int main(int argc, char* argv[]) { .... Model* newModel = GenerateModelNormals(*model, float(smoothAngle * 3.14159265 / 180.0), weldVertices, weldTolerance); .... } 

建议使用诊断程序,但是最好使用标准库中Pi编号的现成常量。

结论


最近,该项目已由发烧友开发,但仍然很受欢迎,并且在培训计划中仍然有需求。 在Internet上,有成千上万个具有不同空间对象的附加组件。 与摩根弗里曼(Morgan Freeman)一起 在“明天后天”和纪录片系列“虫洞”中使用了天体。

我们很高兴通过测试许多开源项目,我们不仅普及了静态代码分析方法,而且还为开放项目世界的发展做出了贡献。 顺便说一句,您还可以使用PVS-Studio分析仪不仅可以测试自己的产品,还可以使用爱好者来测试第三方项目。 为此,您可以使用免费许可的选项之一。

使用静态代码分析器,使您的项目更可靠,更好!



如果您想与说英语的读者分享这篇文章,请使用以下链接:Svyatoslav Razmyslov。 Celestia:虫子的太空历险记

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


All Articles