我们正在开发一个静态代码分析工具,旨在通过一些提示来改进代码。
我们希望找到开发人员忘记检查变量或属性或方法返回的可空性并通过点表示法访问成员的地方,因为它可能会遇到 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)
{
}
}
}
我们如何找到对给定可为 null 类型的成员(变量、字段、属性、方法(的所有访问?
更新:事实上,我们正在搜索 OpCode,它表示成员对 IL 中给定变量的访问权限。这可能吗?
NullReferenceException
的文档有助于记录以下内容:
以下Microsoft中间语言 (MSIL( 说明 投掷
NullReferenceException
:callvirt
,cpblk
,cpobj
,initblk
,ldelem.<type>
,ldelema
,ldfld
,ldflda
,ldind.<type>
,ldlen
,stelem.<type>
,stfld
,stind.<type>
,throw
,unbox
。
这些细分如下:
- 阵列访问:
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 签入策略相关联,请准备好接收来自开发人员和经理的死亡威胁,他们想知道工作效率急剧下降的原因。
像Resharper这样的现有工具添加了很多属性来控制分析是有原因的,并且有一个建议将可空性检查添加到C#本身。在重新发明轮子之前,先知道你正在进入什么。