如何在静态代码分析器实用程序中找到 NullReferenceException 的潜在点?
How to find potential points of NullReferenceException in a static code analyzer utility?
我们正在开发一种静态代码分析工具,旨在通过一些提示改进代码。
我们想找到开发人员忘记检查变量或 属性 或方法 return 的可空性并通过点表示法访问成员的地方,因为它可能会遇到 NullReferenceException。
例如这段代码:
class Program
{
static void Main(string[] args)
{
var human = new Human();
if (human.Name.Length > 10)
{
// Jeez! you have a long name;
}
}
}
public class Human
{
public string Name { get; set; }
}
我们使用 Mono.Cecil 并找到给定程序集中所有类型的所有方法的主体,对于每个方法主体,我们找到它的指令,然后我们检查 Callvirt 操作。然而,这不支持这个例子:
class Program
{
static string name;
static void Main(string[] args)
{
if (name.Length > 10)
{
}
}
}
我们如何找到对给定可空类型的成员(变量、字段、属性、方法)的所有访问?
更新:
事实上,我们正在搜索代表 IL 中给定变量的 成员访问 的操作码。这可能吗?
NullReferenceException
的文档有助于记录以下内容:
The following Microsoft intermediate language (MSIL) instructions
throw NullReferenceException
: callvirt
, cpblk
, cpobj
,
initblk
, ldelem.<type>
, ldelema
, ldfld
, ldflda
,
ldind.<type>
, ldlen
, stelem.<type>
, stfld
, stind.<type>
,
throw
, and unbox
.
这些分解如下:
- 数组访问:
ldelem
、ldelema
、ldlen
、stelem
。数组引用不能是 null
.
- 非数组成员访问:
ldfld
、ldflda
、stfld
。对象引用不能是 null
.
- 方法访问:
callvirt
。对象引用不能是 null
。 属性 访问也是方法访问,因为它调用 属性 getter/setter.
- Pointer/reference 访问:
cpblk
、cpobj
、initblk
、ldind
、stind
。 pointer/reference 不能是 null
。在经过验证的托管代码中,这些操作码通常不会在其参数可能为 null
. 的上下文中使用
- 抛出异常:
throw
。异常引用不能是 null
.
- 开箱:
unbox
。对象引用不能是 null
.
将操作码参数追溯到 variables/fields 完全是另一个问题。这可以任意复杂,因为操作码只关心堆栈上的内容,而不关心它来自哪里。在某些情况下,您可能正在处理表达式 (a[0].SomeMethod().FieldAccess
,其中任何 a
、a[0]
和 a[0].SomeMethod()
可能是 null
,但它们不应该是)。
你最好不要在IL层面检查这个,而是使用Roslyn为你提供语言层面的分析。通过访问源代码可以更简单地生成高质量的反馈。
即便如此,请注意针对可空性的高质量静态分析并不容易。您当然可以编写一个分析器,它会在程序员 可能 忘记检查的所有可能情况下愉快地发出警告,但是如果程序员被迫插入大量的对 显然 从不 null
的引用进行多余检查。如果您将此与 TFS 签入策略相关联,请准备好接受来自开发人员和管理人员的死亡威胁,他们想知道为什么生产力急剧下降。
像 Resharper 这样的现有工具添加大量 attributes for controlling the analysis, and there's a proposal 来为 C# 本身添加可空性检查是有原因的。在重新发明轮子之前知道你正在进入什么。
我们正在开发一种静态代码分析工具,旨在通过一些提示改进代码。
我们想找到开发人员忘记检查变量或 属性 或方法 return 的可空性并通过点表示法访问成员的地方,因为它可能会遇到 NullReferenceException。
例如这段代码:
class Program
{
static void Main(string[] args)
{
var human = new Human();
if (human.Name.Length > 10)
{
// Jeez! you have a long name;
}
}
}
public class Human
{
public string Name { get; set; }
}
我们使用 Mono.Cecil 并找到给定程序集中所有类型的所有方法的主体,对于每个方法主体,我们找到它的指令,然后我们检查 Callvirt 操作。然而,这不支持这个例子:
class Program
{
static string name;
static void Main(string[] args)
{
if (name.Length > 10)
{
}
}
}
我们如何找到对给定可空类型的成员(变量、字段、属性、方法)的所有访问?
更新: 事实上,我们正在搜索代表 IL 中给定变量的 成员访问 的操作码。这可能吗?
NullReferenceException
的文档有助于记录以下内容:
The following Microsoft intermediate language (MSIL) instructions throw
NullReferenceException
:callvirt
,cpblk
,cpobj
,initblk
,ldelem.<type>
,ldelema
,ldfld
,ldflda
,ldind.<type>
,ldlen
,stelem.<type>
,stfld
,stind.<type>
,throw
, andunbox
.
这些分解如下:
- 数组访问:
ldelem
、ldelema
、ldlen
、stelem
。数组引用不能是null
. - 非数组成员访问:
ldfld
、ldflda
、stfld
。对象引用不能是null
. - 方法访问:
callvirt
。对象引用不能是null
。 属性 访问也是方法访问,因为它调用 属性 getter/setter. - Pointer/reference 访问:
cpblk
、cpobj
、initblk
、ldind
、stind
。 pointer/reference 不能是null
。在经过验证的托管代码中,这些操作码通常不会在其参数可能为null
. 的上下文中使用
- 抛出异常:
throw
。异常引用不能是null
. - 开箱:
unbox
。对象引用不能是null
.
将操作码参数追溯到 variables/fields 完全是另一个问题。这可以任意复杂,因为操作码只关心堆栈上的内容,而不关心它来自哪里。在某些情况下,您可能正在处理表达式 (a[0].SomeMethod().FieldAccess
,其中任何 a
、a[0]
和 a[0].SomeMethod()
可能是 null
,但它们不应该是)。
你最好不要在IL层面检查这个,而是使用Roslyn为你提供语言层面的分析。通过访问源代码可以更简单地生成高质量的反馈。
即便如此,请注意针对可空性的高质量静态分析并不容易。您当然可以编写一个分析器,它会在程序员 可能 忘记检查的所有可能情况下愉快地发出警告,但是如果程序员被迫插入大量的对 显然 从不 null
的引用进行多余检查。如果您将此与 TFS 签入策略相关联,请准备好接受来自开发人员和管理人员的死亡威胁,他们想知道为什么生产力急剧下降。
像 Resharper 这样的现有工具添加大量 attributes for controlling the analysis, and there's a proposal 来为 C# 本身添加可空性检查是有原因的。在重新发明轮子之前知道你正在进入什么。