CHƯƠNG 3: BÀI TOÁN ỨNG DỤNG
3.1. Ứng dụng F* vào các bài toán lập trình
3.1.3. Ứng dụng trong cái bài toán làm việc với các tập tin, thư mục
Bắt đầu với một mô hình đơn giản của việc điều khiển truy cập vào các tập tin qua đó giúp ta nhìn nhận rõ hơn các khái niện cơ bản của lập trình với ngôn ngữ F*. Ta có bài toán viết chương trình có thể giúp truy cập hoặc ghi ra các file có sẵn hoặc ghi mới.
Chúng ta muốn một module có thể truy cập dễ dàng vào các file cho trước, cho dù chúng ta có quyền chỉ đọc hoặc có thể ghi được trong khi người khác thì không thể truy cập được. Đảm bảo rằng với tập tin không cho phép truy cập thì không thể truy cập một cách nhầm lẫn được. Dưới đây là một chương trình sử dụng F* đảm bảo rằng có thể truy cập tùy ý một file và đảm bảo các quy định bảo vệ của tập tin đó.
Quy định các quyền, định nghĩa kiểu trong bài toán
module FileName
type filename =string
(*Định nghĩa loại filename với kiểu là string*) module ACLs
(*Định nghĩa danh sách các tập tin được truy cập*) open FileName
let canWrite (f:filename)=
match f with
|"C:/temp/tempfile"->true
|_->false
Định nghĩa khi gọi đến ACLs.canWrite với biến f có kiểu là filename:string chỉ đúng khi tập tin đó như đường dẫn C:/temp/tempfile. Như vậy canWrite là chức năng kiểm tra đối số f có khớp với biểu thức cho trước hay không.
// canRead đồng thời là một hàm với kiểu dữ liệu truyền vào là filename và kết quả trả về là giá trị đúng hoặc sai
val canRead : filename -> Tot bool let canRead (f:filename)=
canWrite f (* Tập tin ghi được thì đồng thời cũng đọc được *)
|| f="C:/public/README" (* Hoặc có đường dẫn được định nghĩa*)
Định nghĩa khi gọi đến ACLs.canRead hoặc tập tin đó có thể ghi được hoặc tập tin đó như đường dẫn "C:/public/README". Chức năng canRead đơn giản chỉ kiểm tra với đối số f thì có thể ghi được hay không. Nếu không kiểm tra tiếp đối số f có khớp với biểu thức cho trước hay không.
Định nghĩa các giao diện, các phương thức được truy cập trong bài toán:
module System.IO open ACLs
open FileName
assumeval read : f:filename{canRead f}->string
assumeval write : f:filename{canWrite f}->string->unit
Để thực thi các chính sách bảo mật trong F*, chúng ta có thể định nghĩa các hàm giao diện (interface) để các hàm khác có thể sử dụng. Như ví dụ trên, chúng ta định nghĩa ra một module System.IO trong đó có 2 chức năng là kiểm tra đọc hoặc kiểm tra ghi (các tính chất tương ứng của các tập tin).
Sử dụng hàm assume val, chúng ta định nghĩa rằng System.IO cung cấp các chức năng đọc ghi, nó có thể truyền vào giá trị, cung cấp cho các module ngoài khi gọi tới chúng, tương tự như những ngôn ngữ lập trình khác.
Bằng với việc gọi đến từ các hàm ngoài, khi khai báo giá trị, chúng ta cũng cung cấp kiểu dữ liệu mà nó cần trả về:
assume val read : f:filename{canRead f} -> string
Hàm đọc đánh giá rằng file đó có thể đọc được và trả lại chuỗi string trong tập tin cần đọc.
assume val write : f:filename{canWrite f} -> string -> unit
Việc đầu tiên của hàm write là đánh giá rằng tập tin đó có thể ghi được, sau đó là chuỗi các giá trị cần ghi vào file và trả về unit, tương tự như các hàm void trong C hay C#.
Ghép nối chương trình và kiểm tra các đoạn mã thực thi:
1module UntrustedClientCode 2 open System.IO
3 open FileName
4 let passwd ="C:/etc/password"
5 letreadme ="C:/public/README"
6 let tmp ="C:/temp/tempfile"
7 let staticChecking ()=
8 let v1 = read tmp in 9 let v2 = read readme in
10 // let v3 = read passwd in -- invalid read, fails type-checking 11 write tmp "hello!"
12 // write passwd "junk" -- invalid write , fails type-checking 13 exception InvalidRead
14 valcheckedRead : filename ->string 15 let checkedRead f =
16 if ACLs.canRead f then System.IO.read f 17 else raise InvalidRead
18 exception InvalidWrite 19 let checkedWrite f s =
20 if ACLs.canWrite f then System.IO.write f s else raise InvalidWrite 21 let dynamicChecking ()=
22 let v1 = checkedRead tmp in 23 let v2 = checkedRead readme in
24 // let v3 = checkedRead passwd in – Đoạn mã có thể lỗi 25 assume valcheckedWrite : filename ->string->unit 26 checkedWrite tmp "hello!";
27 // checkedWrite passwd "junk" – Đoạn mã có thể lỗi
Hàm checkedRead và hàm checkedWrite là bảo mật khi được biên dịch bởi F* vì khi đó chỉ thực hiện read hoặc write tập tin khi chúng chạy qua hàm ACLs.canRead hay
ACLs.canWrite. Ngược lại với việc sử dụng let v1 = read tmp in trình biên dịch sẽ báo lỗi check kiểu do trả về false tại hàm ACLs.canWrite và không có giá trị trả về phù hợp tại hàm ACLs.canRead (có thể hiểu tương tự như try catch trong các ngôn ngữ khác).