云中的PVS-Studio:CircleCI

图片2

这是我们系列文章中有关将PVS-Studio静态分析仪与云CI系统结合使用的新内容。 今天,我们将看看另一个服务CircleCI。 我们将把Kodi媒体播放器应用程序作为测试项目,看看是否可以在其源代码中找到任何有趣的错误。
注意事项 先前有关将PVS-Studio与云CI系统集成的文章:


在设置工作环境和检查分析报告之前,我想对我们将要使用和检查的软件说几句话。

CircleCI是用于自动化软件构建,测试和部署的云CI服务。 它支持在容器中以及Windows,Linux和macOS上的虚拟机上的项目构建。

Kodi是一个免费的开源跨平台媒体播放器应用程序。 它允许用户播放和查看大多数流媒体,例如来自Internet的视频,音乐,播客和视频,以及来自本地和网络存储媒体的所有常见数字媒体文件。 它支持通过插件使用主题和外观以及功能扩展。 Kodi适用于Windows,Linux,macOS和Android。

PVS-Studio是一种静态分析器,用于检测用C,C ++,C#和Java编写的应用程序的源代码中的错误和潜在漏洞。 分析仪可在Windows,Linux和macOS上运行。

设定


首先,我们需要转到CircleCI主页,然后单击“注册”

图片1

在下一页,我们提供使用GitHub或Bitbucket帐户进行授权的权限。 我们选择GitHub并转到CircleCI授权页面。

图片3

授权应用程序后(通过单击绿色按钮“ Authorize circleci”),我们将重定向到“ Welcome to CircleCI!”。 页面:

图片4

在这里,我们可以立即指定希望CircleCI构建的项目。 我们勾选存储库,然后单击“关注”。

添加存储库后,CircleCI将自动开始构建过程,但是由于我们的存储库中还没有配置文件,因此构建作业将中止并显示错误消息。

图片5

在添加配置文件之前,我们需要添加几个包含分析器许可证数据的变量。 为此,我们单击左侧工具栏上的“设置”,在“组织”部分中选择“项目”,然后单击我们项目名称右边的齿轮按钮。 将会出现一个设置窗口。

图片6

我们转到“环境变量”页面。 在这里,我们创建两个变量PVS_USERNAMEPVS_KEY ,它们包含用户名和分析器许可证密钥。

图片7

开始构建时,CircleCI从存储在.circleci / config.yml存储库中的文件中读取作业配置。 让我们添加它。

首先,我们需要指定分析器将在其上运行的虚拟机的映像。 图像的完整列表在此处

version: 2 jobs: build: machine: image: ubuntu-1604:201903-01 

接下来,我们添加必要的存储库以apt并安装项目的依赖项:

 steps: - checkout - run: sudo -- sh -c " add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends && add-apt-repository -y ppa:wsnipex/vaapi && add-apt-repository -y ppa:pulse-eight/libcec && apt-get update" - run: sudo apt-get install -y automake autopoint build-essential cmake curl default-jre gawk gdb gdc gettext git-core gperf libasound2-dev libass-dev libbluray-dev libbz2-dev libcap-dev libcdio-dev libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils nasm pmount python-dev python-imaging python-sqlite rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget 

添加PVS-Studio存储库并安装分析仪:

 - run: wget -q -O - https://files.viva64.com/etc/pubkey.txt | sudo apt-key add - && sudo wget -O /etc/apt/sources.list.d/viva64.list https://files.viva64.com/etc/viva64.list - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y" 

然后我们建立依赖关系:

 - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local 

之后,我们在构建目录中生成Makefile:

 - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. 

下一步是建立并开始项目分析。

首先,我们创建一个分析器许可证文件。 另一个命令将开始跟踪编译器的项目构建。

跟踪之后的下一个命令将按此方式运行分析。 如果使用PVS-Studio的演示版,请使用以下参数启动它:

--disableLicenseExpirationCheck

最终命令将分析仪的报告文件转换为html报告:

 - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY} - run: pvs-studio-analyzer trace -- make -j2 -C build/ - run: pvs-studio-analyzer analyze -j2 -l PVS.lic -o PVS-Studio.log --disableLicenseExpirationCheck - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log 

测试结束后,我们将保存报告:

 - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/ - store_artifacts: path: ./PVS_Result 

这是.circleci / config.yml文件的完整文本:

 version: 2.1 jobs: build: machine: image: ubuntu-1604:201903-01 steps: - checkout - run: sudo -- sh -c " add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends && add-apt-repository -y ppa:wsnipex/vaapi && add-apt-repository -y ppa:pulse-eight/libcec && apt-get update" - run: sudo apt-get install -y automake autopoint build-essential cmake curl default-jre gawk gdb gdc gettext git-core gperf libasound2-dev libass-dev libbluray-dev libbz2-dev libcap-dev libcdio-dev libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils nasm pmount python-dev python-imaging python-sqlite rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget - run: wget -q -O - https://files.viva64.com/etc/pubkey.txt | sudo apt-key add – && sudo wget -O /etc/apt/sources.list.d/viva64.list https://files.viva64.com/etc/viva64.list - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y" - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY} - run: pvs-studio-analyzer trace -- make -j2 -C build/ - run: pvs-studio-analyzer analyze -j2 -l PVS.lic -o PVS-Studio.log --disableLicenseExpirationCheck - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/ - store_artifacts: path: ./PVS_Result 

此文件上传到资源库后,CircleCI将自动开始构建。

图片12

作业完成后,可以在“工件”选项卡上下载带有分析结果的文件。

图片11

分析结果


好的,现在让我们看一下分析仪输出的一些警告。

PVS-Studio警告: V504分号';'的可能性很大 “ return”关键字后缺失。 AdvancedSettings.cpp:1476

 void CAdvancedSettings::SetExtraArtwork(const TiXmlElement* arttypes, std::vector<std::string>& artworkMap) { if (!arttypes) return artworkMap.clear(); const TiXmlNode* arttype = arttypes->FirstChild("arttype"); .... } 

代码格式建议以下执行逻辑:

  • 如果arttypes是空指针,则该方法返回; 否则 ,返回false。
  • 如果arttypes是非null指针, 则将清除articleMap向量,然后执行一些操作。

但是缺少';' 字符会破坏一切,实际的执行逻辑如下:

  • 如果arttypes是空指针, 则将清除articleMap向量,并返回该方法;
  • 如果arttypes是非null指针,则程序将执行接下来要执行的任何操作,但不会清除artsMapMap向量。

简而言之,这种情况看起来确实像个错误。 毕竟,您几乎不希望任何人编写诸如return ArtworkMap.clear();之类的表达式 :)。

PVS-Studio警告:

  • V547表达式“ lastsector”始终为false。 udf25.cpp:636
  • V547表达式“ lastsector”始终为false。 udf25.cpp:644
  • V571定期检查。 'if(lastsector)'条件已在第636行中得到验证。udf25.cpp:644

 int udf25::UDFGetAVDP( struct avdp_t *avdp) { .... uint32_t lastsector; .... lastsector = 0; // <= .... for(;;) { .... if( lastsector ) { // <= V547 lbnum = lastsector; terminate = 1; } else { //! @todo Find last sector of the disc (this is optional). if( lastsector ) // <= V547 lbnum = lastsector - 256; else return 0; } } .... } 

请注意标有// <=的斑点。 为lastsector变量分配值0,然后在两个if语句中将其用作条件表达式。 由于该值在循环或赋值之间都不会改变,因此控制永远不会进入两个if语句的else分支。

但是,这也可能意味着开发人员尚未实现预期的功能(请注意待办事项 )。

顺便说一句,您可能已经注意到,此摘要一次触发了三个警告。 但是,即使对于一个代码的许多警告,对于某些用户来说也无法令人信服,他们将继续相信分析器是错误的……我的一个团队成员在一篇帖子中对此进行了详细讨论:“ PVS-Studio用户支持 “ :)。

PVS-Studio警告: V547表达式'values.size()!= 2'始终为false。 GUIControlSettings.cpp:1174

 bool CGUIControlRangeSetting::OnClick() { .... std::vector<CVariant> values; SettingConstPtr listDefintion = settingList->GetDefinition(); switch (listDefintion->GetType()) { case SettingType::Integer: values.push_back(m_pSlider-> GetIntValue(CGUISliderControl::RangeSelectorLower)); values.push_back(m_pSlider-> GetIntValue(CGUISliderControl::RangeSelectorUpper)); break; case SettingType::Number: values.push_back(m_pSlider-> GetFloatValue(CGUISliderControl::RangeSelectorLower)); values.push_back(m_pSlider-> GetFloatValue(CGUISliderControl::RangeSelectorUpper)); break; default: return false; } if (values.size() != 2) return false; SetValid(CSettingUtils::SetList(settingList, values)); return IsValid(); } 

values.size()!= 2 check在这里是多余的,因为此条件表达式将始终为false 。 实际上,如果执行进入switch语句的case分支之一,则将两个元素添加到向量中,并且由于其最初为空,因此其大小自然将等于2; 否则(即,如果执行了默认分支),该方法将返回。

PVS-Studio警告: V547表达式' prio == 0x7fffffff'始终为true。 DBusReserve.cpp:57

 bool CDBusReserve::AcquireDevice(const std::string& device) { .... int prio = INT_MAX; .... res = dbus_bus_request_name( m_conn, service.c_str(), DBUS_NAME_FLAG_DO_NOT_QUEUE | (prio == INT_MAX ? 0 : DBUS_NAME_FLAG_ALLOW_REPLACEMENT), // <= error); .... } 

prio变量初始化为INT_MAX值,然后将其用作prio == INT_MAX比较中的三元运算符的操作数,尽管在初始化后其值不会更改。 这意味着prio == INT_MAX表达式为true ,三元运算符将始终返回0。

PVS-Studio警告:

  • V575潜在的空指针被传递到“ memcpy”函数中。 检查第一个参数。 检查行:39,38。DVDOverlayImage.h:39
  • V575潜在的空指针被传递到“ memcpy”函数中。 检查第一个参数。 检查行:44,43。DVDOverlayImage.h:44

 CDVDOverlayImage(const CDVDOverlayImage& src) : CDVDOverlay(src) { Data = (uint8_t*)malloc(src.linesize * src.height); memcpy(data, src.data, src.linesize * src.height); // <= if(src.palette) { palette = (uint32_t*)malloc(src.palette_colors * 4); memcpy(palette, src.palette, src.palette_colors * 4); // <= } .... } 

两种警告具有相同的模式:由malloc函数返回的指针在memcpy函数中进一步使用,而无需先检查NULL

有人可能会争辩说, malloc永远不会返回空指针,如果确实如此,则使应用程序崩溃会更好。 这是一个单独讨论的主题,但是不管您的意见如何,我建议阅读我的队友的这篇文章:“ 为什么检查malloc函数返回的内容很重要 ”。

如果愿意,可以自定义分析器,以使其不假定malloc可以返回空指针-这将阻止它输出这种类型的警告。 可以在这里找到更多详细信息。

PVS-Studio警告: V522可能会取消引用潜在的空指针“入口”。 检查行:985、981。emu_msvcrt.cpp:985

 struct dirent *dll_readdir(DIR *dirp) { .... struct dirent *entry = NULL; entry = (dirent*) malloc(sizeof(*entry)); if (dirData->curr_index < dirData->items.Size() + 2) { if (dirData->curr_index == 0) strncpy(entry->d_name, ".\0", 2); .... } 

此示例与上一个示例相似。 malloc函数返回的指针存储在entry变量中,然后使用该变量而无需事先进行空检查( entry-> d_name )。

PVS-Studio警告: V773在没有释放内存的情况下退出了“ progressHandler”指针的可见性范围。 可能发生内存泄漏。 PVRGUIChannelIconUpdater.cpp:94

 void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const { .... CPVRGUIProgressHandler* progressHandler = new CPVRGUIProgressHandler(g_localizeStrings.Get(19286)); for (const auto& group : m_groups) { const std::vector<PVRChannelGroupMember> members = group->GetMembers(); int channelIndex = 0; for (const auto& member : members) { progressHandler->UpdateProgress(member.channel->ChannelName(), channelIndex++, members.size()); .... } progressHandler->DestroyProgress(); } 

新的运算符返回了progressHandler指针的值。 但是此指针没有删除运算符。 这意味着内存泄漏。

PVS-Studio警告: V557阵列可能超限。 'idx'索引指向数组边界之外。 PlayerCoreFactory.cpp:240

 std::vector<CPlayerCoreConfig *> m_vecPlayerConfigs; bool CPlayerCoreFactory::PlaysVideo(const std::string& player) const { CSingleLock lock(m_section); size_t idx = GetPlayerIndex(player); if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size()) return false; return m_vecPlayerConfigs[idx]->m_bPlaysVideo; } 

if语句通过在大小检查条件为true时使方法返回,从而将m_vecPlayerConfigs向量的大小限制在一定范围内。 结果,当执行到达最后一个return语句时, m_vecPlayerConfigs向量的大小将在指定范围内[1; idx]。 但是几行之后,程序在idx处索引了向量: m_vecPlayerConfigs [idx]-> m_bPlaysVideo 。 这意味着如果idx等于向量的大小,我们将索引超出有效范围。

让我们用Platinum库代码中的几个示例结束本文。

PVS-Studio警告: V542考虑检查一个奇怪的类型转换:'bool'到'char *'。 PltCtrlPoint.cpp:1617

 NPT_Result PLT_CtrlPoint::ProcessSubscribeResponse(...) { .... bool subscription = (request.GetMethod().ToUppercase() == "SUBSCRIBE"); .... NPT_String prefix = NPT_String::Format(" PLT_CtrlPoint::ProcessSubscribeResponse %ubscribe for service \"%s\" (result = %d, status code = %d)", (const char*)subscription?"S":"Uns", // <= (const char*)service->GetServiceID(), res, response?response->GetStatusCode():0); .... } 

开发人员对操作的优先级有错误的假设。 强制转换为const char *的不是三元运算符( 预订?“ S”:“ Uns” )返回的结果,而是预订变量。 至少这看起来很奇怪。

PVS-Studio警告: V560条件表达式的一部分始终为false:c =='\ t'。 NptUtils.cpp:863

 NPT_Result NPT_ParseMimeParameters(....) { .... case NPT_MIME_PARAMETER_PARSER_STATE_NEED_EQUALS: if (c < ' ') return NPT_ERROR_INVALID_SYNTAX; // END or CTLs are invalid if (c == ' ' || c == '\t') continue; // ignore leading whitespace .... } 

空格字符的代码为0x20,制表符字符的代码为0x09。 因此, c =='\ t'子表达式将始终为false,因为这种情况已被c <''检查覆盖(如果为true,则将导致函数返回)。

结论


如本文所示,我们成功地通过PVS-Studio在另一个CI系统(CircleCI)上进行了分析。 我邀请您下载并在自己的项目上试用分析仪。 如果您对PVS-Studio的设置或使用有任何疑问,请随时与我们联系 -我们将很乐意为您提供帮助。

并且,当然,我们希望您没有错误的代码。 :)

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


All Articles