在 Visual Studio版本16.8 预览3 , 我们 是 添加 惯性导航与制导 一 很少的 C++代码分析的安全规则 那可以 找到 一些常见的错误,可能导致 虫子范围 从简单的断开特征到 代价高昂的安全漏洞。 这些新规则是围绕着在 生产 软件via 安全审查和事故 需要 昂贵的 服务 . 作为安全和法规遵从性要求的一部分,微软的每一款软件都运行这些规则。
这篇博文将介绍有关 VARIANT
以及它的兄弟类型,比如 VARIANTARG
,或 PROPVARIANT
. 为了配合新规则, 我们 已经建立了一个代码分析扩展名 VariantClear
,那个 检测违反这些新规则的行为 在代码中 . 它被命名为 VariantClear
因为它检测到的主要规则是关于滥用 VariantClear
功能。
这个 VariantClear
延伸 检测 和报告 以下警告:
- C33001型 : 变量“var”在 未初始化
- C33004型 : 变量“var”,标记为 出去 在被发现之前 已初始化
- C33005型 : 变量“var”作为输入或输入/输出参数提供,但未提供 已初始化
虽然VisualStudioVersion16.8Preview 3已经包含了VariantClear扩展,但默认情况下它还没有启用。若要启用此扩展名,请将以下行添加到项目文件或 Microsoft.CodeAnalysis.Extensions.props
文件位于 MSBuildMicrosoftVCv160
Visual Studio安装位置中的文件夹:
如果要将其添加到单个项目文件中,请将其添加到所有其他文件之后 <PropertyGroup>
元素:
<PropertyGroup Condition="'$(ConfigurationType)'!='Utility' and '$(ConfigurationType)'!='Makefile'"> <EspXtensions Condition="'$(EnableVariantClear)'!='false'">VariantClear.dll;$(EspXtensions)</EspXtensions> </PropertyGroup>
如果要修改VisualStudio安装,可以将其添加到 Microsoft.CodeAnalysis.Extensions.props
文件,在 HResultCheck
:
<EspXtensions Condition="'$(EnableVariantClear)'!='false'">VariantClear.dll;$(EspXtensions)</EspXtensions>
请注意,如果您修复或重新安装VisualStudio,或升级到更高版本,这可能会被覆盖。当我们在VisualStudio中启用此扩展时,请继续关注更新。
差异清除 规则
VARIANT
是一个非常方便的结构,允许 交换 使用单个 结构 类型。在任何给定的时间,它都可以保存可选类型之一,或者不保存任何值。包含的数据的类型或它不包含值的事实由 VARIANT::vt
成员。
A VARIANT
对象在使用或传递给其他代码之前需要显式初始化。否则,这将导致随机数据被访问和使用,根据访问的内容和使用方式,会导致不同的问题。
A VARIANT
当不再需要对象时,也需要将其清除。否则,它可能会留下一些资源,泄漏资源或让其他人在资源的预期生命周期后错误地访问和使用资源。
初始化 VARIANT
对象通常通过调用完成 VariantInit
功能。清理 VARIANT
对象主要通过调用完成 VariantClear
功能。
VARIANT struct有一些包装器类型,可以使它更容易和更安全地使用。 CComVariant
和 _variant_t
. 它们的默认构造函数初始化正在创建的实例,并通常通过调用 VariantInit
,传递当前实例。它们的析构函数通常通过调用 VariantClear
,传递当前实例。
差异清除 规则试图在使用变量实例之前强制执行正确初始化变量实例的一般规则,包括清理它们。
警告C33001
当未初始化的 VARIANT
传递到一个API中,该API清除 VARIANT
例如 VariantClear
. 这些api期望 VARIANT
在清除之前初始化。 不幸的是,开发人员经常忘记这一步。
下面是一个简化的示例:
#include <Windows.h> HRESULT foo(bool some_condition) { VARIANT var; if (some_condition) { //... VariantInit(&var); //... } VariantClear(&var); // C33001 }
此代码将触发C33001警告,因为 VARIANT
var
仅当 some_condition
是 true
. 如果条件是 false
,它在传递给时将不会初始化 VariantClear
功能。要解决这个问题,我们必须确保我们正在打电话 VariantClear
只为 VARIANT
已初始化的:
#include <Windows.h> HRESULT foo(bool some_condition) { VARIANT var; if (some_condition) { //... VariantInit(&var); //... VariantClear(&var); // C33001 } }
警告C33004
此警告在以下情况下触发: VARIANT
参数 _Out_
萨尔( 源代码注释语言 )注释可能不会在输入时初始化,它会传递给API,例如 VariantClear
需要初始化的 VARIANT
.
注释为的参数 _Out_
调用函数时不需要初始化。它将在函数返回时初始化。有关SAL注释的更多详细信息,请参阅 到 SAL注释 .
在代码分析期间 _Out_ VARIANT
为了安全起见,假定参数未初始化。如果将此参数传递给函数,例如 VariantClear
需要初始化的 VARIANT
对象时,它将尝试清理或使用随机类型的数据,可能是在随机内存位置。下面是一个简化的示例:
#include <Windows.h> HRESULT t2(_Out_ VARIANT* pv) { // ...... VariantClear(pv); // C33004. pv is assumed uninitialized. // ...... return S_OK; }
为了解决这个问题,我们 不得不 确保初始化 _Out_ VARIANT
参数或将其传递给另一个需要初始化 VARIANT
实例:
#include <Windows.h> void t2(_Out_ VARIANT* pv) { VariantInit(pv); // ...... VariantClear(pv); // OK // ...... }
警告C33005
当未初始化的 VARIANT
作为仅输入或输入/输出参数传递给函数,例如 const VARIANT*
类型。 举个例子:
#include <Windows.h> void bar(VARIANT* v); // v is assumed to be input/output void foo() { VARIANT v; bar(&v); // C33005 // ...... VariantClear(&v); // OK, assumed to be initialized by bar }
请注意,checker假设函数采用非常量 VARIANT*
参数将初始化 VARIANT
对象,以避免生成嘈杂的警告。
同样,要解决这个问题,我们只需要确保初始化 VARIANT
对象,然后将其作为仅输入或输入-输出参数传递给另一个函数:
#include <Windows.h> void bar(VARIANT* v); // v is assumed to be input/output void foo() { VARIANT v; VariantInit(&v); bar(&v); // OK // ...... VariantClear(&v); // OK, assumed to be initialized by bar }
理解了C33005规则之后,应该更清楚为什么C33004只针对一个输出报告(也就是说,用注释) _Out_
(SAL注释) 参数 . 为了 一 仅输入 或 输入-输出 参数 ,通过 一 n 未初始化 VARIANT
将违反C33005规则。
在Visual Studio中启用新规则
通过为项目选择不同的规则集,可以按如下方式在Visual Studio中启用这些规则:
规则ID | 扩展 | 本机最小规则 | 本地推荐规则 | 所有规则 |
C33001型 | 差异清除 | 十 | 十 | 十 |
C33004型 | 差异清除 | 十 | 十 | |
C33005型 | 差异清除 | 十 | 十 |
把你的钱给我们 反馈
看看这些新添加的规则,让我们知道它们是否帮助你编写更安全的C++。请继续关注我们在VisualStudio未来版本中添加的更多安全规则。
下载 Visual Studio 2019版本16.8预览版3 今天 试试看。我们很乐意收到您的来信,帮助我们确定优先级并为您构建合适的功能。我们可以通过以下评论联系到您, 开发者社区 , 和 推特( @视觉 ). 提交bug或建议特性的最佳方法是通过开发者社区。