如何在静态代码分析器实用程序中找到 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.

这些分解如下:

  • 数组访问:ldelemldelemaldlenstelem。数组引用不能是 null.
  • 非数组成员访问:ldfldldfldastfld。对象引用不能是 null.
  • 方法访问:callvirt。对象引用不能是 null。 属性 访问也是方法访问​​,因为它调用 属性 getter/setter.
  • Pointer/reference 访问:cpblkcpobjinitblkldindstind。 pointer/reference 不能是 null。在经过验证的托管代码中,这些操作码通常不会在其参数可能为 null.
  • 的上下文中使用
  • 抛出异常:throw。异常引用不能是 null.
  • 开箱:unbox。对象引用不能是 null.

将操作码参数追溯到 variables/fields 完全是另一个问题。这可以任意复杂,因为操作码只关心堆栈上的内容,而不关心它来自哪里。在某些情况下,您可能正在处理表达式 (a[0].SomeMethod().FieldAccess,其中任何 aa[0]a[0].SomeMethod() 可能是 null,但它们不应该是)。

你最好不要在IL层面检查这个,而是使用Roslyn为你提供语言层面的分析。通过访问源代码可以更简单地生成高质量的反馈。

即便如此,请注意针对可空性的高质量静态分析并不容易。您当然可以编写一个分析器,它会在程序员 可能 忘记检查的所有可能情况下愉快地发出警告,但是如果程序员被迫插入大量的对 显然 从不 null 的引用进行多余检查。如果您将此与 TFS 签入策略相关联,请准备好接受来自开发人员和管理人员的死亡威胁,他们想知道为什么生产力急剧下降。

像 Resharper 这样的现有工具添加大量 attributes for controlling the analysis, and there's a proposal 来为 C# 本身添加可空性检查是有原因的。在重新发明轮子之前知道你正在进入什么。