首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么Dialyzer没有发现这个代码错误?

为什么Dialyzer没有发现这个代码错误?
EN

Stack Overflow用户
提问于 2012-08-07 18:08:14
回答 2查看 1.5K关注 0票数 6

我已经根据this教程创建了下面的代码片段。最后两行(feed_squid(FeederRP)feed_red_panda(FeederSquid))显然违反了定义的约束,但Dialyzer发现它们还好。这非常令人失望,因为这正是我希望通过工具执行静态分析来捕获的错误类型。

本教程提供了一个解释:

在用错误的馈线调用函数之前,首先用正确的类型调用函数。对于R15B01,Dialyzer不会发现这个代码的错误。观察到的行为是,一旦对给定函数的调用在函数的体内成功,Dialyzer将忽略相同代码单元中的稍后错误。

这种行为的理由是什么?我理解成功输入背后的哲学是“永不哭泣狼”,但在目前的场景中,Dialyzer显然忽略了有意定义的函数规范(在它看到之前正确调用了这些函数之后)。我理解代码不会导致运行时崩溃。我能以某种方式迫使透析器始终认真对待我的功能规格吗?如果没有,是否有一个工具可以做到这一点?

代码语言:javascript
运行
复制
-module(zoo).
-export([main/0]).

-type red_panda() :: bamboo | birds | eggs | berries.
-type squid() :: sperm_whale.
-type food(A) :: fun(() -> A).

-spec feeder(red_panda) -> food(red_panda());
            (squid) -> food(squid()).
feeder(red_panda) ->
    fun() ->
            element(random:uniform(4), {bamboo, birds, eggs, berries})
    end;
feeder(squid) ->
    fun() -> sperm_whale end.

-spec feed_red_panda(food(red_panda())) -> red_panda().
feed_red_panda(Generator) ->
    Food = Generator(),
    io:format("feeding ~p to the red panda~n", [Food]),
    Food.

-spec feed_squid(food(squid())) -> squid().
feed_squid(Generator) ->
    Food = Generator(),
    io:format("throwing ~p in the squid's aquarium~n", [Food]),
    Food.

main() ->
    %% Random seeding
    <<A:32, B:32, C:32>> = crypto:rand_bytes(12),
    random:seed(A, B, C),
    %% The zoo buys a feeder for both the red panda and squid
    FeederRP = feeder(red_panda),
    FeederSquid = feeder(squid),
    %% Time to feed them!
    feed_squid(FeederSquid),
    feed_red_panda(FeederRP),
    %% This should not be right!
    feed_squid(FeederRP),
    feed_red_panda(FeederSquid).
EN

回答 2

Stack Overflow用户

发布于 2012-08-10 10:40:03

尽量减少这个示例,我有以下两个版本:

第一个透析器能捕捉到的:

代码语言:javascript
运行
复制
-module(zoo).
-export([main/0]).

-type red_panda_food() :: bamboo.
-type squid_food()     :: sperm_whale.

-spec feed_squid(fun(() -> squid_food())) -> squid_food().
feed_squid(Generator) -> Generator().

main() ->
    %% The zoo buys a feeder for both the red panda and squid
    FeederRP = fun() -> bamboo end,
    FeederSquid = fun() -> sperm_whale end,
    %% CRITICAL POINT %%
    %% This should not be right!
    feed_squid(FeederRP),
    %% Time to feed them!
    feed_squid(FeederSquid)

然后那个没有警告的:

代码语言:javascript
运行
复制
    [...]
    %% CRITICAL POINT %%
    %% Time to feed them!
    feed_squid(FeederSquid)
    %% This should not be right!
    feed_squid(FeederRP).

对于它可以捕捉的版本,Dialyzer的警告如下:

代码语言:javascript
运行
复制
zoo.erl:7: The contract zoo:feed_squid(fun(() -> squid_food())) -> squid_food() cannot be right because the inferred return for feed_squid(FeederRP::fun(() -> 'bamboo')) on line 15 is 'bamboo'
zoo.erl:10: Function main/0 has no local return

..。这是一种宁愿相信自己对用户更严格规范的判断的情况。

对于未捕获的版本,Dialyzer假设feed_squid/1参数的类型fun() -> bamboofun() -> none()的超级类型(如果不在feed_squid/1中调用,它仍然是一个有效的参数)。在推断出类型之后,Dialyzer无法知道是否在函数中实际调用了传递的闭包。

如果使用-Woverspecs选项,透析器仍会发出警告:

代码语言:javascript
运行
复制
zoo.erl:7: Type specification zoo:feed_squid(fun(() -> squid_food())) -> squid_food() is a subtype of the success typing: zoo:feed_squid(fun(() -> 'bamboo' | 'sperm_whale')) -> 'bamboo' | 'sperm_whale'

..。警告说,没有什么能阻止此函数处理其他馈线或任何给定的馈线!如果该代码检查闭包的预期输入/输出,而不是泛型的,那么我非常肯定Dialyzer会发现这种滥用。从我的观点来看,如果您的实际代码检查错误输入,而不是依赖类型规范和Dialyzer (它从来没有承诺要查找所有错误),情况会好得多。

警告:深奥的部分紧随其后!

在第一种情况下报告错误而不是第二种情况的原因与模块-局部细化的进展有关。最初,函数feed_squid/1成功地键入了(fun() -> any()) -> any()。在第一种情况下,函数feed_squid/1将首先使用FeederRP进行细化,并且肯定会返回bamboo,立即伪造规范并停止对main/0的进一步分析。在第二部分中,函数feed_squid/1将首先使用FeederSquid进行细化,肯定会返回sperm_whale,然后使用FeederSquidFeederRP进行细化,然后返回sperm_whalebamboo。然后使用FeederRP调用时,预期的返回值成功地输入为sperm_whalebamboo。规格然后承诺它将是sperm_whale和透析器接受它。另一方面,这个论点应该是fun() -> bamboo | sperm_whale成功地输入,它是fun() -> bamboo,所以只剩下fun() -> bamboo了。当对照规范(fun() -> sperm_whale)检查这一点时,Dialyzer假设参数可以是fun() -> none()。如果您从未调用feed_squid/1中传递的函数(Dialyzer的类型系统不作为信息保存),并且您在规范中保证始终返回sperm_whale,那么一切都很好!

可以“修正”的是,当作为参数传递的闭包实际用于调用和警告时,类型系统将被扩展为注意,在这种情况下,“生存”类型推断的某些部分的唯一方法是fun(...) -> none()

票数 5
EN

Stack Overflow用户

发布于 2012-08-08 09:42:44

(请注意,我在这里推测一下。我还没有详细阅读透析器代码)。

“正常”的类型检查器具有类型检查可判定的优点。当类型检查器终止时,我们可以问“这个程序是否打得很好”并得到“是”或“否”。透析器的情况并非如此。它本质上是为了解决停滞不前的问题。其结果是,会有一些明显错误的程序,但仍会在透析器的夹持下滑落。

然而,这并不是其中之一:)

问题是两方面的。成功类型说:“如果此函数正常终止,它的类型是什么?”在上面,我们的feed_red_panda/1函数终止任何匹配任意类型Afun (() -> A)的参数。我们可以调用feed_red_panda(fun erlang:now/0),它也应该能工作。因此,我们对main/0中函数的两个调用不会引起问题。他们都终止了。

问题的第二部分是“你是否违反了规范?”注意,规格通常不作为事实在透析器中使用。它推断类型本身,并使用推理模式而不是您的规范。每当调用函数时,都会使用参数对其进行注释。在我们的例子中,它将被注释为两个生成器类型:food(red_panda()), food(squid())。然后根据这些注释进行函数本地分析,以确定您是否违反了规范。由于注释中存在正确的参数,所以我们必须假定在代码的某些部分正确地使用了该函数。它也是用乌贼调用的,这可能是代码的工件,而由于其他情况,代码从来没有被调用过。因为我们是函数本地的,所以我们不知道,并且给程序员带来怀疑的好处。

如果您将代码更改为只使用squid生成器进行错误的调用,那么我们就会发现规范不一致。因为我们知道唯一可能的呼叫站点违反了规范。如果将错误的调用移动到另一个函数,也不会找到它。因为注释仍然在函数上,而不是在调用站点上。

人们可以想象未来透析器的一个变体,它解释了每个呼叫站点可以单独处理的事实。由于透析器也在随着时间的推移而变化,它可能在将来能够处理这种情况。但目前,这是一个错误,将溜走。

关键是要注意,透析器不能用作“良好类型的检查器”。您不能使用它在程序上强制执行结构。你得自己去做。如果您希望进行更多的静态检查,可能可以为Erlang编写一个类型检查器,并在代码库的一部分上运行它。但是,在代码升级和分发方面会遇到麻烦,这是不容易处理的。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11851639

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档