我们正在开发一个静态代码分析工具,旨在通过一些提示来改进代码。
我们希望找到开发人员忘记检查变量或属性或方法返回的可空性并通过点表示法访问成员的位置,因为它可能会遇到 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。 - 指针/引用访问:
cpblk
、cpobj
、initblk
、ldind
、stind
。指针/引用不得为null
。在经过验证的托管代码中,这些操作码通常不会在其参数可能为null
的上下文中使用。 - 抛出异常:
throw
。异常引用不能是null
。 - 拆箱:
unbox
。对象引用不能是null
。
将操作码参数追溯到变量/字段完全是另一个问题。这可以任意复杂,因为操作码只关心堆栈上的内容,而不关心它来自哪里。在某些情况下,您可能会处理表达式( a[0].SomeMethod().FieldAccess
,其中 a
、 a[0]
和 a[0].SomeMethod()
中的任何一个都可能是 null
,而它们不应该是)。
您最好不要在IL级别上检查这一点,而是使用Roslyn为您提供语言级别上的分析。通过访问源代码,生成高质量的反馈变得更加简单。
即便如此,请注意,对可空性进行高质量的静态分析并不容易。您当然可以编写一个分析器,它会在程序员可能忘记检查的每种可能情况下愉快地发出警告,但是如果程序员被迫插入大量多余的检查,那么这样的分析器几乎毫无用处。 显然永远不会是 null
的引用。如果您将其与 TFS checkin 策略联系起来,请准备好收到来自开发人员和管理人员的死亡威胁,他们想知道为什么生产力急剧下降。
像 Resharper 这样的现有工具添加大量 attributes 来控制分析是有原因的,并且还有一个 proposal 来向 C# 本身添加可空性检查。在重新发明轮子之前先了解您要做什么。
关于c# - 如何在静态代码分析器实用程序中找到可能出现 NullReferenceException 的点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39248041/