2017-09-17 13 views
5

Dialyzer phiên bản 2.9. Erts 7.3. OTP 18.dialyzer không phát hiện vi phạm bảo vệ khi chức năng được xuất

Trong mã erlang giả tạo như sau:

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

test() -> 
    f1(1). 

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

Khi lọc là điều chạy qua đoạn mã trên nó cảnh báo rằng mã sẽ không làm việc như kiểm tra bảo vệ (X> 5) có thể không bao giờ thành công .

Tuy nhiên, khi tôi bỏ ghi chú dòng thứ 3 và xuất khẩu, hàm dialyzer của f1/1 không còn phát ra bất kỳ cảnh báo nào.

Tôi nhận ra rằng khi f1/1 được xuất, không thể nào dialyzer biết rằng mệnh đề bảo vệ sẽ thất bại vì các máy khách bên ngoài có thể sử dụng nó. Tuy nhiên, tại sao nó không còn có thể xác định rằng kiểm tra/0 đang sử dụng f1/1 không chính xác?

Trả lời

7

Máy quay số có một số giới hạn làm bộ kiểm tra loại. Dialyzer không phải là nghiêm ngặt typer, nó là một lỏng lẻo typer. Điều này có nghĩa là nó sẽ chỉ cho bạn một cảnh báo khi nó tìm thấy một cái gì đó rõ ràng là sai với cách mà một hàm được khai báo trái ngược với một trường hợp nào đó mà nó cho rằng người gọi có thể đang làm điều gì đó xấu.

Nó sẽ cố gắng suy ra những điều về các trang web gọi điện thoại, nhưng nó không thể vượt ra ngoài những tuyên bố loại cơ bản nào có thể truyền đạt. Vì vậy, giá trị số nguyên có thể được định nghĩa là neg_integer(), pos_integer(), non_neg_integer() hoặc integer(), nhưng trừ khi bạn đã xác định rõ ràng ranh giới với giá trị pháp lý không có cách nào để xác định phạm vi tùy ý từ, 5..infinity, nhưng bạn có thể xác định phạm vi như 5..10 và nhận kết quả bạn mong đợi.

Phần lẻ là bảo vệ cung cấp một số thông tin cho Dialyzer, bởi vì nó là một bạo lực thực sự/lỏng lẻo, gánh nặng thực sự là trên bộ mã hóa để xác định các chức năng với định nghĩa đủ chặt chẽ.

Sau đây là cách những điều này diễn ra trong mã thực tế + đầu ra Dialyzer (chịu với tôi, nó là một chút của một màn hình dài để hiển thị tất cả những điều này hoàn toàn, nhưng không có gì chứng tỏ vấn đề có liên quan tốt hơn so với code):

gốc vấn đề

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

test() -> 
    f(1). 

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

ngày Dialyzer:

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) 

Vì vậy, trong một thế giới khép kín chúng ta có thể s ee Dialyzer sẽ quay trở lại với người gọi vì nó có một trường hợp giới hạn.

biến thể thứ hai

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

test() -> 
    f(1). 

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

Dialyzer nói:

done (passed successfully) 

Trong một thế giới mở, nơi người gọi có thể là bất cứ ai gửi bất cứ điều gì, không có nỗ lực để quay lại và kiểm tra một không khai báo , phạm vi không bị chặn.

biến thể thứ ba

-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 nói:

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) 

Trong một thế giới mở nơi chúng tôi có một declarable mở phạm vi (trong trường hợp này, tập các số nguyên dương) trang web cuộc gọi vi phạm sẽ được tìm thấy.

Fourth biến

-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 nói:

done (passed successfully) 

Trong một thế giới mở, nơi chúng tôi có một bảo vệ nhưng vẫn không khai báo phạm vi chúng ta thấy rằng Dialyzer sẽ một lần nữa không tìm thấy người gọi vi phạm. Đây là biến thể quan trọng nhất của tất cả, theo ý kiến ​​của tôi - bởi vì chúng tôi biết rằng Dialyzer làm lấy các gợi ý từ các vệ sĩ kiểm tra các loại, nhưng rõ ràng là không lấy các gợi ý từ bộ phận kiểm tra phạm vi số. Vì vậy, chúng ta hãy xem nếu chúng ta khai báo một bị chặn, nhưng tùy ý, phạm vi ...

biến Fifth

-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 nói:

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) 

Và ở đây chúng tôi thấy rằng nếu chúng ta spoon- feed Dialyzer nó sẽ làm công việc của nó như mong đợi.

Tôi không thực sự chắc chắn liệu điều này được coi là "lỗi" hay "hạn chế của sự lỏng lẻo của Dialyzer". Điểm chính của địa chỉ Dialyzer đau là loại gốc không thành công và không phải là giới hạn số.

Tất cả những gì đã nói ...

Khi tôi đã từng có vấn đề này trong thực tế, mã làm việc trong một dự án thực tế hữu ích trong thế giới thực - Tôi đã biết rõ trước cho dù tôi đang đối phó với dữ liệu hợp lệ hay không, và trong rất ít trường hợp tôi không tôi sẽ luôn viết thư này cho một trong hai:!

  1. sụp đổ hoàn toàn (Never trả về một kết quả xấu Chỉ cần chết thay Đây là tôn giáo của chúng ta vì một lý do.)
  2. Trả về giá trị được bao bọc của biểu mẫu {ok, Value} | {error, out_of_bounds} và để người gọi quyết định phải làm gì với nó (điều này mang lại cho họ thông tin tốt hơn trong mọi trường hợp).

Ví dụ được bảo vệ có liên quan - ví dụ cuối cùng ở trên có bảo vệ bị chặn sẽ là phiên bản thích hợp của chức năng có thể bị lỗi.

-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}. 
+0

Cảm ơn bạn đã trả lời rất kỹ lưỡng. Tôi nhận ra nó không phải là một lỗi như vậy nhưng nó có vẻ không phù hợp.Có một ví dụ trong cuốn sách của Joe Armstrong cho thấy cách dialyzer đưa loại/phạm vi giai thừa (N) thành non_neg_integer() (mặc dù nó được xuất khẩu vào thế giới mở) và có thể phát hiện lỗi gọi với số âm. Trên khuôn mặt của nó có vẻ như một kỳ tích phân tích ấn tượng hơn nhiều so với việc phát hiện phạm vi của một người bảo vệ. –

+1

@DavidJ Bạn nói đúng: nó cảm thấy không phù hợp, ngay cả khi nó phù hợp với các kiểu cơ bản của ngôn ngữ đánh máy. Tôi nghĩ rằng nó có thể được cải thiện hợp lý, mặc dù. Tôi muốn làm cho Dialyzer chuyển đổi phạm vi bảo vệ thành gợi ý cụ thể, mở rộng ngôn ngữ typespec để chấp nhận phạm vi không bị chặn (Tôi nghĩ rằng nó hoàn toàn hợp lý để thể hiện phạm vi như '69..infinity' hoặc' infinity..37.6') và khiếu nại nếu một phạm vi được chỉ định xung đột với một người bảo vệ (một là thời gian chạy và một là thời gian biên dịch trước - vì vậy bảo vệ thực tế sẽ là rào cản thời gian chạy). – zxq9

+1

Câu hỏi liên quan: https://stackoverflow.com/questions/43307949/erlang-dialyzer-integer-ranges – aronisstav

Các vấn đề liên quan