透析器在导出功能时未检测到防护措施

时间:2017-09-17 10:07:15

标签: erlang dialyzer

Dialyzer版本2.9。 Erts 7.3。 OTP 18。

在以下设计的erlang代码中:

-module(dialBug).
-export([test/0]).
%-export([f1/1]). % uncomment this line

test() ->
    f1(1).

f1(X) when X > 5 ->
    X*2.

当透析器运行在上面的代码上时,它会警告代码不起作用,因为防护测试(X> 5)永远不会成功。

然而,当我取消注释第3行并导出f1 / 1功能时,透析器不再发出任何警告。

我意识到当导出f1 / 1时,透析器不可能知道guard子句会因为外部客户端可以使用它而失败。但是,为什么它不能再确定test / 0是否正确使用f1 / 1?

1 个答案:

答案 0 :(得分:6)

Dialyzer作为类型检查器有一些限制。 Dialyzer不是严格的 typer,它是松散的 typer。这意味着它只会在发现函数被声明的方式明显错误时给你一个警告,而不是某种情况下它会推断调用者可能做坏事。

它会尝试推断调用网站的内容,但它不能超越基本的类型规范声明可以传达的内容。因此,整数值可以定义为neg_integer()pos_integer()non_neg_integer()或任何integer(),但除非您明确定义了合法值的边界,否则无法定义任意范围,例如5..infinity,但可以定义范围,例如5..10,并获得您期望的结果。

这个奇怪的部分是,虽然守卫向Dialyzer提供了一些信息,因为它是一个宽容/宽松的形式,编码器的真正负担是指定功能,其定义足够严格,可以检测到呼叫站点的错误。 / p>

以下是这些内容在实际代码+ Dialyzer输出中的表现(与我一起,它有点长屏幕以完全显示所有这些,但没有比代码更好地展示相关问题):

原始问题

-module(dial_bug1).
-export([test/0]).
%-export([f/1]).

test() ->
    f(1).

f(X) when X > 5 ->
    X * 2.

透析日:

dial_bug1.erl:5: Function test/0 has no local return
dial_bug1.erl:8: Function f/1 has no local return
dial_bug1.erl:8: Guard test X::1 > 5 can never succeed
 done in 0m1.42s
done (warnings were emitted)

因此,在一个封闭的世界中,我们可以看到Dialyzer将回调给调用者,因为它的案例有限。

第二个变体

-module(dial_bug2).
-export([test/0]).
-export([f/1]).

test() ->
    f(1).

f(X) when X > 5 ->
    X * 2.

Dialyzer说:

done (passed successfully)

在一个开放的世界中,呼叫者可以在任何人发送任何东西,没有努力回溯并检查未声明的,无限范围

第三种变体

-module(dial_bug3).
-export([test/0]).
-export([f/1]).

-spec test() -> integer().

test() ->
    f(-1).


-spec f(X) -> Result
    when X      :: pos_integer(),
         Result :: pos_integer().

f(X) when X > 5 ->
    X * 2.

Dialyzer说:

dial_bug3.erl:7: Function test/0 has no local return
dial_bug3.erl:8: The call dial_bug3:f(-1) breaks the contract (X) -> Result when X :: pos_integer(), Result :: pos_integer()
 done in 0m1.28s
done (warnings were emitted)

在一个开放的世界中,我们有一个可声明的开放范围(在这种情况下,正整数集)将找到违规的呼叫网站。

第四种变体

-module(dial_bug4).
-export([test/0]).
-export([f/1]).

-spec test() -> integer().

test() ->
    f(1).


-spec f(X) -> Result
    when X      :: pos_integer(),
         Result :: pos_integer().

f(X) when 5 =< X, X =< 10  ->
    X * 2.

Dialyzer说:

done (passed successfully)

在一个开放的世界中,我们有一个受保护的但仍未声明的范围,我们发现Dialyzer将再次找不到违规的来电者。在我看来,这是所有内容中最重要的变体 - 因为我们知道Dialyzer 确实从检查类型的警卫那里获取提示,但显然从中获取提示数字范围检查警卫。所以,让我们看看我们是否宣布了一个有界但是任意的范围......

第五种变体

-module(dial_bug5).
-export([test/0]).
-export([f/1]).

-spec test() -> integer().

test() ->
    f(1).


-spec f(X) -> Result
    when X      :: 5..10,
         Result :: pos_integer().

f(X) when 5 =< X, X =< 10  ->
    X * 2.

Dialyzer说:

dial_bug5.erl:7: Function test/0 has no local return
dial_bug5.erl:8: The call dial_bug5:f(1) breaks the contract (X) -> Result when X :: 5..10, Result :: pos_integer()
 done in 0m1.42s
done (warnings were emitted)

在这里我们看到,如果我们用勺子喂食Dialyzer,它将按预期完成它的工作。

我不确定这是否被视为&#34; bug&#34;或者&#34; Dialyzer松散的约束&#34;。疼痛的主要问题Dialyzer地址是失败的原生类型,而不是数字界限

所有这一切......

当我在实际项目中的实际工作代码中遇到过这个问题时,我已经提前知道我是否处理有效数据,并且在极少数情况下我不知道#39; t我总是写这个:

  1. 直接崩溃(从不返回错误的结果!只是死了!这是我们的宗教信仰。)
  2. 返回{ok, Value} | {error, out_of_bounds}形式的包装值,让调用者决定如何处理它(这样可以在每种情况下为他们提供更好的信息)。
  3. 一个有保护的例子是相关的 - 上面的最后一个例子有一个有界的守卫将是一个可崩溃函数的正确版本。

    -spec f(X) -> Result
        when X      :: 5..10,
             Result :: {ok, pos_integer()}
                     | {error, out_of_bounds}.
    
    f(X) 5 =< X, X =< 10 ->
        Value = X * 2,
        {ok, Value};
    f(_) ->
        {error, out_of_bounds}.