Celestia是一个三维空间模拟器。 对空间的模拟可以从三个维度探索我们的宇宙。 Celestia在Windows,Linux和macOS上可用。 该项目很小,PVS-Studio几乎没有发现任何缺陷。 尽管有这个事实,我们还是要注意它,因为它是一个受欢迎的教育项目,并且对改进它很有用。 顺便说一下,这个程序被用于流行的电影,系列和节目中以显示空间。 反过来,这一事实对代码质量提出了要求。
引言
Celestia项目的官方网站提供了其详细说明。 源代码可在
GitHub上获得 。 分析器检查了166个.cpp文件,不包括库和测试。 该项目很小,但是发现的缺陷值得注意。
为了进行源代码分析,我们使用了
PVS-Studio静态代码分析器。 Celestia和PVS-Studio都是跨平台的。 我们在Windows平台上分析了该项目。 通过使用
Vcpkg -Microsoft库管理器获取依赖关系来构建项目很简单。 根据评论,它不如
柯南的能力,但该程序使用起来也很方便。
分析结果
警告1V501 '<'运算符的左侧和右侧有相同的子表达式: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)
复制代码时容易出错。 我们在每条评论中都会对此进行介绍。 显然,在这种情况下,只有静态代码分析才能提供帮助。
程序员复制了条件表达式,但没有完全对其进行编辑。 正确的版本很可能如下所示:
if (a.nAttributes < b.nAttributes) return true; if (b.nAttributes < a.nAttributes) return false;
关于这个主题的有趣研究:“
比较函数中的邪恶 ”。
警告2V575'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字节的内存。
警告3V595在针对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进行比较之前,
目标指针已被解除引用两行。 这样的代码可能会导致错误。
警告4V702类应始终从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) { } };
分析器已通过
private修饰符(默认设置)检测到从
std :: exception类继承的类。 这样的继承很危险,因为
std :: exception异常不会由于非公共继承而被捕获。 结果,异常处理程序的行为不符合预期。
警告5V713在针对同一逻辑表达式中的nullptr进行验证之前,在逻辑表达式中使用了指针“ s”。 winmain.cpp 3031
static char* skipUntilQuote(char* s) { while (*s != '"' && s != '\0') s++; return s; }
在条件表达式的一个片段中,程序员忘记了取消引用
s指针。 原来是指针的比较,而不是其值。 在这种情况下,这没有任何意义。
警告6V773在不释放'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
警告7V547表达式'!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变量,因此可以删除下面的检查,从而使代码更紧凑。
警告8V556比较不同枚举类型的值:开关(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运算符中混合在一起。 因此,在一个片段中比较了不同类型的枚举:
LabelVerticalAlignment和
AlignCenter 。
警告9V581彼此并排放置的'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); } .... }
分析器已连续检测到两个相同的条件表达式。 要么出错,要么可以将两个条件组合为一个条件,从而使代码更简单。
警告10V668测试'dp'指针是否为null没有意义,因为内存是使用'new'运算符分配的。 如果内存分配错误,将生成异常。 windatepicker.cpp 625
static LRESULT DatePickerCreate(HWND hwnd, CREATESTRUCT& cs) { DatePicker* dp = new DatePicker(hwnd, cs); if (dp == NULL) return -1; .... }
将
new运算符返回的指针值与null进行比较。 如果操作员无法分配内存,则根据C ++标准,将引发异常
std :: bad_alloc() 。 那么对null的检查是没有意义的。
另外三项类似的检查:
- V668测试“ modes”指针是否为null没有意义,因为使用“ new”运算符分配了内存。 如果内存分配错误,将生成异常。 winmain.cpp 2967
- V668没有针对空测试'dropTarget'指针的意义,因为使用'new'运算符分配了内存。 如果内存分配错误,将生成异常。 winmain.cpp 3272
- V668测试“ appCore”指针是否为null没有意义,因为使用“ new”运算符分配了内存。 如果内存分配错误,将生成异常。 winmain.cpp 3352
警告11V624正在使用常数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上有成千上万个具有不同空间对象的插件。 在电影《
明天之后 》和纪录片系列《
与摩根·弗里曼的蠕虫中穿越 》中使用了天体。
我们很高兴,通过使用开源代码检查
各种项目,我们不仅促进了静态代码分析方法,而且为开源项目的开发做出了贡献。 顺便说一句,您还可以使用PVS-Studio分析仪不仅可以测试自己的产品,还可以使用爱好者来测试第三方项目。 为此,您可以使用免费许可的
选项之一。
使用静态代码分析器,使您的项目更可靠,更好!