我们不断地让 PVS-Studio 的使用变得更加方便。 我们的分析器现在可以在 Chocolatey(Windows 的包管理器)中使用。 我们相信这将促进PVS-Studio的部署,特别是在云服务中。 为了不走得太远,让我们检查一下同一个 Chocolatey 的源代码。 Azure DevOps 将充当 CI 系统。
以下是我们关于与云系统集成主题的其他文章的列表:
PVS-Studio 走向云端:Azure DevOps PVS-Studio 走向云端:Travis CI PVS-Studio 走向云端:CircleCI PVS-Studio 走向云端:GitLab CI/CD
我建议您关注第一篇关于与 Azure DevOps 集成的文章,因为在这种情况下省略了一些要点,以免重复。
那么,本文的主角:
关于使用 Chocolatey
您可以在此处查看如何安装包管理器本身
安装最新版本分析器的命令:
choco install pvs-studio
安装特定版本的 PVS-Studio 软件包的命令:
choco install pvs-studio --version=7.05.35617.2075
默认情况下,仅安装分析器的核心,即 Core 组件。 所有其他标志(Standalone、JavaCore、IDEA、MSVS2010、MSVS2012、MSVS2013、MSVS2015、MSVS2017、MSVS2019)都可以使用 --package-parameters 传递。
将使用 Visual Studio 2019 插件安装分析器的命令示例:
choco install pvs-studio --package-parameters="'/MSVS2019'"
现在让我们看一下在Azure DevOps下方便使用分析器的示例。
调整
让我提醒您,有一个单独的部分介绍了注册帐户、创建构建管道以及将您的帐户与 GitHub 存储库中的项目同步等问题。
首先,让我们设置一个启动触发器,表明我们仅针对以下内容的更改启动 主 分支:
trigger:
- master
接下来我们需要选择一个虚拟机。 目前,它将是 Microsoft 托管的 Windows Server 2019 和 Visual Studio 2019 代理:
pool:
vmImage: 'windows-latest'
让我们继续讨论配置文件的主体(块 步骤)。 尽管你不能在虚拟机中安装任意软件,但我没有添加 Docker 容器。 我们可以添加 Chocolatey 作为 Azure DevOps 的扩展。 为此,让我们转到
在这里您需要选择我们将添加扩展程序的位置,然后单击按钮 Install 安装.
安装成功后,点击 继续组织:
您现在可以在窗口中看到 Chocolatey 任务的模板 任务 编辑配置文件时 天蓝色管道.yml:
单击 Chocolatey 并查看字段列表:
这里我们需要选择 安装 与球队一起在球场上。 在 Nuspec 文件名 指示所需包的名称 - pvs-studio。 如果不指定版本,则会安装最新版本,这完全适合我们。 让我们按下按钮 加 我们将在配置文件中看到生成的任务。
steps:
- task: ChocolateyCommand@0
inputs:
command: 'install'
installPackageId: 'pvs-studio'
接下来,让我们继续讨论文件的主要部分:
- task: CmdLine@2
inputs:
script:
现在我们需要使用分析器许可证创建一个文件。 这里 PVS名称 и PVSKEY – 我们在设置中指定其值的变量名称。 他们将存储 PVS-Studio 登录名和许可证密钥。 要设置它们的值,请打开菜单 变量->新变量。 让我们创建变量 PVS名称 用于登录和 PVSKEY 为分析仪键。 不要忘记选中该框 将此值保密 为 PVSKEY。 命令代码:
сall "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe" credentials
–u $(PVSNAME) –n $(PVSKEY)
让我们使用存储库中的 bat 文件构建项目:
сall build.bat
让我们创建一个文件夹,用于存储包含分析器结果的文件:
сall mkdir PVSTestResults
让我们开始分析该项目:
сall "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe"
–t .srcchocolatey.sln –o .PVSTestResultsChoco.plog
我们使用 PlogСonverter 实用程序将报告转换为 html 格式:
сall "C:Program Files (x86)PVS-StudioPlogConverter.exe"
–t html –o PVSTestResults .PVSTestResultsChoco.plog
现在您需要创建一个任务以便上传报告。
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: PVSTestResults
artifactName: PVSTestResults
condition: always()
完整的配置文件如下所示:
trigger:
- master
pool:
vmImage: 'windows-latest'
steps:
- task: ChocolateyCommand@0
inputs:
command: 'install'
installPackageId: 'pvs-studio'
- task: CmdLine@2
inputs:
script: |
call "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe"
credentials –u $(PVSNAME) –n $(PVSKEY)
call build.bat
call mkdir PVSTestResults
call "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe"
–t .srcchocolatey.sln –o .PVSTestResultsChoco.plog
call "C:Program Files (x86)PVS-StudioPlogConverter.exe"
–t html –o .PVSTestResults .PVSTestResultsChoco.plog
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: PVSTestResults
artifactName: PVSTestResults
condition: always()
我们点击一下 保存->保存->运行 运行任务。 让我们转到任务选项卡来下载报告。
Chocolatey 项目仅包含 37615 行 C# 代码。 让我们看看发现的一些错误。
检测结果
警告 N1
分析仪警告:
public abstract class CrytpoHashProviderSpecsBase : TinySpec
{
....
protected CryptoHashProvider Provider;
....
public override void Context()
{
Provider = Provider = new CryptoHashProvider(FileSystem.Object);
}
}
分析器检测到变量对其自身的赋值,这是没有意义的。 最有可能的是,应该用其他变量来代替其中一个变量。 好吧,或者这是一个拼写错误,并且可以简单地删除额外的分配。
警告 N2
分析仪警告:
public static PlatformType get_platform()
{
switch (Environment.OSVersion.Platform)
{
case PlatformID.MacOSX:
{
....
}
case PlatformID.Unix:
if(file_system.directory_exists("/Applications")
& file_system.directory_exists("/System")
& file_system.directory_exists("/Users")
& file_system.directory_exists("/Volumes"))
{
return PlatformType.Mac;
}
else
return PlatformType.Linux;
default:
return PlatformType.Windows;
}
}
运营商差异 & 来自运营商 && 如果表达式的左边是 false,那么右侧仍然会被计算,在这种情况下这意味着不必要的方法调用 系统目录存在.
在所考虑的片段中,这是一个小缺陷。 是的,可以通过将 & 运算符替换为 && 运算符来优化此条件,但从实际角度来看,这不会产生任何影响。 但是,在其他情况下,当表达式的右侧使用不正确/无效的值时,& 和 && 之间的混淆可能会导致严重的问题。 例如,在我们的错误集合中,
if ((k < nct) & (s[k] != 0.0))
即使指数 k 不正确,它将用于访问数组元素。 结果会抛出异常 索引超出范围异常.
警告 N3、N4
分析仪警告:
分析仪警告:
public static string
prompt_for_confirmation(.... bool shortPrompt = false, ....)
{
....
if (shortPrompt)
{
var choicePrompt = choice.is_equal_to(defaultChoice) //1
?
shortPrompt //2
?
"[[{0}]{1}]".format_with(choice.Substring(0, 1).ToUpperInvariant(), //3
choice.Substring(1,choice.Length - 1))
:
"[{0}]".format_with(choice.ToUpperInvariant()) //0
:
shortPrompt //4
?
"[{0}]{1}".format_with(choice.Substring(0,1).ToUpperInvariant(), //5
choice.Substring(1,choice.Length - 1))
:
choice; //0
....
}
....
}
在这种情况下,三元运算符的运算背后有一个奇怪的逻辑。 让我们仔细看看:如果满足我用数字 1 标记的条件,那么我们将继续执行条件 2,该条件始终是 true,这意味着第 3 行将被执行。如果条件 1 结果为假,那么我们将转到标有数字 4 的行,其中的条件也始终为 true,这意味着将执行第5行。因此,注释0标记的条件永远不会被满足,这可能不完全是程序员期望的操作逻辑。
警告 N5
分析仪警告:
private static string GetArgumentName (...., string description)
{
string[] nameStart;
if (maxIndex == 1)
{
nameStart = new string[]{"{0:", "{"};
}
else
{
nameStart = new string[]{"{" + index + ":"};
}
for (int i = 0; i < nameStart.Length; ++i)
{
int start, j = 0;
do
{
start = description.IndexOf (nameStart [i], j);
}
while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false);
....
return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
}
}
诊断适用于该线路:
while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false)
由于变量 j 上面几行初始化为零,三元运算符将返回值 false。 由于这种情况,循环体将仅执行一次。 在我看来,这段代码根本没有按照程序员的意图工作。
警告 N6
分析仪警告:
private void remove_nuget_cache_for_package(....)
{
if (!config.AllVersions && installedPackageVersions.Count > 1)
{
const string allVersionsChoice = "All versions";
if (installedPackageVersions.Count != 1)
{
choices.Add(allVersionsChoice);
}
....
}
....
}
这里有一个奇怪的嵌套条件: 安装的PackageVersions.Count!= 1这将永远是 true。 通常,此类警告表示代码中存在逻辑错误,而在其他情况下,它仅表示冗余检查。
警告 N7
分析仪警告:
public static bool arguments_contain_sensitive_information(string
commandArguments)
{
return commandArguments.contains("-install-arguments-sensitive")
|| commandArguments.contains("-package-parameters-sensitive")
|| commandArguments.contains("apikey ")
|| commandArguments.contains("config ")
|| commandArguments.contains("push ")
|| commandArguments.contains("-p ")
|| commandArguments.contains("-p=")
|| commandArguments.contains("-password")
|| commandArguments.contains("-cp ")
|| commandArguments.contains("-cp=")
|| commandArguments.contains("-certpassword")
|| commandArguments.contains("-k ")
|| commandArguments.contains("-k=")
|| commandArguments.contains("-key ")
|| commandArguments.contains("-key=")
|| commandArguments.contains("-apikey")
|| commandArguments.contains("-api-key")
|| commandArguments.contains("-apikey")
|| commandArguments.contains("-api-key");
}
编写这段代码的程序员复制并粘贴了最后两行,但忘记编辑它们。 因此,Chocolatey 用户无法应用该参数 密钥 还有几种方法。 与上面的参数类似,我可以提供以下选项:
commandArguments.contains("-apikey=");
commandArguments.contains("-api-key=");
在任何具有大量源代码的项目中,复制粘贴错误迟早都会出现,而解决这些错误的最佳工具之一就是静态分析。
PS 与往常一样,此错误往往出现在多行条件的末尾:)。 参见出版物“
警告 N8
分析仪警告:
public virtual ConcurrentDictionary<string, PackageResult> get_outdated(....)
{
....
var pinnedPackageResult = outdatedPackages.GetOrAdd(
packageName,
new PackageResult(installedPackage,
_fileSystem.combine_paths(
ApplicationParameters.PackagesLocation,
installedPackage.Id)));
....
if ( installedPackage != null
&& !string.IsNullOrWhiteSpace(installedPackage.Version.SpecialVersion)
&& !config.UpgradeCommand.ExcludePrerelease)
{
....
}
....
}
经典错误:对象优先 已安装的包 使用然后检查 空。 此诊断告诉我们程序中的两个问题之一: 已安装的包 永远不平等 空,这是值得怀疑的,然后检查是多余的,或者我们可能会在代码中遇到严重错误 - 尝试访问空引用。
结论
所以我们又迈出了一小步 - 现在使用 PVS-Studio 变得更加容易和方便。 我还想说,Chocolatey 是一个很好的包管理器,代码中的错误很少,使用 PVS-Studio 时错误可能会更少。
我们邀请
PS
在发表之前,我们将这篇文章发送给了 Chocolatey 开发人员,他们收到了很好的反馈。 我们没有发现任何关键的东西,但例如,他们喜欢我们发现的与“api-key”密钥相关的错误。
如果您想与英语读者分享这篇文章,请使用翻译链接:Vladislav Stolyarov。
来源: habr.com