查看源代码 Dialyzer

简介

范围

Dialyzer 是一个静态分析工具,用于识别软件差异,例如确定的类型错误、由于编程错误而无法访问的代码以及单个 Erlang 模块或整个代码库中不必要的测试。

可以从命令行和 Erlang 中调用 Dialyzer。

持久查找表

Dialyzer 将分析结果存储在持久查找表 (PLT) 中。然后,PLT 可以用作后续分析的起点。建议使用您正在使用的 Erlang/OTP 应用程序构建 PLT,但也包括您经常使用的自己的应用程序。

PLT 是使用 Dialyzer 的 --build_plt 选项构建的。以下命令为 Erlang/OTP 构建建议的最小 PLT

dialyzer --build_plt --apps erts kernel stdlib mnesia

Dialyzer 会查找名为 DIALYZER_PLT 的环境变量,并将 PLT 放置在此位置。如果未设置此类变量,Dialyzer 会将 PLT 放置在 filename:basedir(user_cache, "erlang") 文件夹中名为 .dialyzer_plt 的文件中。也可以使用 --plt--output_plt 选项指定放置位置。

可以使用 --add_to_plt 选项将信息添加到现有 PLT。如果您还想将 Erlang 编译器包含在 PLT 中并将其放置在新的 PLT 中,请使用以下命令

dialyzer --add_to_plt --apps compiler --output_plt my.plt

然后,您可以将您最喜欢的应用程序 my_app 添加到新的 PLT

dialyzer --add_to_plt --plt my.plt -r my_app/ebin

但是您意识到在此 PLT 中包含 Erlang 编译器是不必要的

dialyzer --remove_from_plt --plt my.plt --apps compiler

稍后,当您修复了应用程序 my_app 中的一个 bug 时,您想更新 PLT,以便下次运行 Dialyzer 时它是最新的。在这种情况下,运行以下命令

dialyzer --check_plt --plt my.plt

然后,Dialyzer 会重新分析更改的文件以及依赖于这些文件的文件。请注意,下次使用此 PLT 运行 Dialyzer 时,会自动执行此一致性检查。使用 --check_plt 选项执行一致性检查,而不进行任何其他分析。

要获取有关 PLT 的信息,请使用以下选项

dialyzer --plt_info

要指定哪个 PLT,请使用 --plt 选项。

要将输出打印到文件,请使用 --output_file 选项。

请注意,在操作 PLT 时不会发出警告。要在 PLT 的(重新)分析期间启用警告,请使用 --get_warnings 选项。

从命令行使用 Dialyzer

Dialyzer 具有用于自动化使用的命令行版本。请参阅 dialyzer

从 Erlang 中使用 Dialyzer

Dialyzer 也可以直接从 Erlang 中使用。请参阅 dialyzer

Dialyzer 的分析模型

Dialyzer 在经典类型检查器和更通用的静态分析工具之间运行:它检查并使用函数规范,但并不要求它们,并且它可以发现模块之间的 bug,这些 bug 会考虑分析中的程序的数据流。这意味着 Dialyzer 可以在复杂的代码中发现真正的 bug,并且在面对缺失的规范或关于代码库的有限信息时是务实的,只报告它可以证明有可能在运行时引起真正问题的那些问题。这意味着 Dialyzer 有时不会报告每个 bug,因为它并非总是能够找到这个证明。

Dialyzer 如何使用函数规范

Dialyzer 会推断模块中所有顶层函数的类型。如果模块在源代码中也给出了规范,Dialyzer 会将推断的类型与规范进行比较。比较会检查每个参数和返回值,以确保推断的类型和指定的类型重叠——也就是说,这些类型至少有一个可能的公共运行时值。请注意,Dialyzer 不会检查一种类型是否包含另一种类型的值的子集,或者它们是否完全相等:这允许 Dialyzer 进行简化假设,以保持性能并避免报告可能在运行时成功发生的程序流。

如果推断的类型和指定的类型不重叠,Dialyzer 会警告说,该规范相对于实现是无效的。但是,如果它们确实重叠,Dialyzer 将假设给定函数的正确类型是推断的类型和指定的类型的交集(理由是用户可能知道 Dialyzer 本身无法推断出的东西)。这意味着,如果用户给出一个与 Dialyzer 推断的类型重叠的函数规范,但该规范更具限制性,Dialyzer 将信任这些限制。这可能会在后续其他地方生成因错误限制的规范而产生的错误。

示例

不重叠的参数

-spec foo(boolean()) -> string().
%% Dialyzer will infer: foo(integer()) -> string().
foo(N) ->
    integer_to_list(N).

由于规范中的参数类型与 Dialyzer 推断的类型不同,Dialyzer 将生成以下警告

some_module.erl:7:2: Invalid type specification for function some_module:foo/1.
 The success typing is some_module:foo
          (integer()) -> string()
 But the spec is some_module:foo
          (boolean()) -> string()
 They do not overlap in the 1st argument

不重叠的返回值

-spec bar(a | b) -> atom().
%% Dialyzer will infer: bar(a | b) -> binary().
bar(a) -> <<"a">>;
bar(b) -> <<"b">>.

由于规范中的返回值与 Dialyzer 推断的返回值不同,Dialyzer 将生成以下警告

some_module.erl:11:2: Invalid type specification for function some_module:bar/1.
 The success typing is some_module:bar
          ('a' | 'b') -> <<_:8>>
 But the spec is some_module:bar
          ('a' | 'b') -> atom()
 The return types do not overlap

重叠的规范和推断类型

-spec baz(a | b) -> non_neg_integer().
%% Dialyzer will infer: baz(b | c | d) -> -1 | 0 | 1.
baz(b) -> -1;
baz(c) -> 0;
baz(d) -> 1.

Dialyzer 将“信任”该规范,并使用该规范和推断类型的交集

baz(b) -> 0 | 1.

请注意,一旦规范和推断类型相交,baz/1 的参数中的 cd 以及推断类型返回中的 -1 都被删除。这可能会导致为后续函数发出警告。

例如,如果像这样调用 baz/1

call_baz1(A) ->
    case baz(A) of
        -1 -> negative;
        0 -> zero;
        1 -> positive
    end.

Dialyzer 将生成以下警告

some_module.erl:25:9: The pattern
          -1 can never match the type
          0 | 1

如果像这样调用 baz/1

call_baz2() ->
    baz(a).

Dialyzer 将生成以下警告

some_module.erl:30:1: Function call_baz2/0 has no local return
some_module.erl:31:9: The call t:baz
         ('a') will never return since it differs in the 1st argument
               from the success typing arguments:
         ('b' | 'c' | 'd')

反馈和错误报告

我们非常欢迎用户的反馈!如果您注意到任何奇怪的事情,特别是如果 Dialyzer 报告任何误报的差异,请打开一个 issue,描述症状以及如何重现它们。