查看源代码 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
的参数中的 c
和 d
以及推断类型返回中的 -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,描述症状以及如何重现它们。