OCommand : 型安全な シェルプログラミングのための 領域特化言語の提案 朝倉 泉 増原 英彦 青谷 知幸 東京工業大学 数理・計算科学専攻 1 OCommand • OCaml上で安全にシェルコマンドを 呼び出すための処理系 – コマンドの仕様を書くためのDSL – コマンドの結果にアクセスする書式を変換す る処理系 • コマンド出力へのアクセスに対する 安全性を保証 2 lsコマンドを利用するプログラム例 • ls -lの出力からサイズの合計が与えられた閾 値より小さいファイル集合を求めたい • 例 閾値=10000Byte ファイルサイズ $ ls –l -rw-r----1 root admin 2682 Dec -rw-r----1 root admin 2459 Jan -rw-r----1 root admin 2397 Jan -rw-r--r-1 root wheel 607 Jan -rw-r----1 root admin 12217 Feb -rw-r----1 root admin 12986 Feb {perm:perm; hard:int; ... ;size:int; ... ファイル名 17 20:37 a 10 14:54 b 10 14:55 c 16 07:53 d 4 17:45 e 5;name:string} 00:02 f 各欄が適切なデータ型に変換されたレコード型として扱いたい 3 lsコマンドを利用するプログラム例 • コマンドはオプション集合を引数に取る関数(コマンド 関数) • コマンド関数の戻り値はレコード(出力行レコード) のリスト let collect_files size = let files = Ls.command (add_l empty) in let rec iter cur_size acc files = match files with 指定されているオプションの集合 | [] -> acc (オプション集合) | file :: files -> let cur_size = cur_size + file..size in if cur_size > size then acc else iter cur_size (file..name :: acc) files in iter 0 [] files ;; 4 目標1/5. コマンドの関数化 let collect_files size = 1. コマンドをオプションを引 数に取る関数として扱う let files = Ls.command (add_l empty) in let rec iter cur_size acc files = emptyは空のオプション集合 match files with | [] -> acc add_lはオプション集合に lオプションを追加する関数 | file :: files -> let cur_size = cur_size + file..size in if cur_size > size then acc else iter cur_size (file..name :: acc) files in iter 0 [] files ;; 5 目標2/5. 出力のレコード化 let collect_files size = let files = Ls.command (add_l let rec iter cur_size acc files = match files with | [] -> acc | file :: files -> empty) in let cur_size = cur_size + file..size in if cur_size > size then acc else iter cur_size (file..name :: acc) files in iter 0 [] files ;; 2. コマンド出力が自動的に レコードに変換される 6 目標3/5. レコード型の柔軟な変化 let collect_files size = let files = Ls.command (add_i (add_l empty)) in let rec iter cur_size acc files = iオプションを追加して match files with | [] -> acc 出力行レコードにinodeという | file :: files -> int型のフィールドを追加 3.オプションが変化する let cur_size = cur_size + file..size in と出力レコードの型も if cur_size > size then acc 適切に変化する else iter cur_size (file..name :: acc) files in iter 0 [] files ;; 7 目標4/5. フィールドアクセスの検査 4.ユーザがアクセスしたフィール let collect_files size = ドが存在することが検査できる let files = Ls.command (add_i let rec iter cur_size acc files = $ ls files –i with match | [] -> a acc 10358 | file :: files -> empty) in 305049 b let cur_size = cur_size + file..size in 301392 c if cur_size > size then acc 940030 elsediter cur_size (file..name :: acc) files in61038 e iter 0 [] files 573013 f 出力にsizeフィールドが無いので ;; コンパイルエラー 8 目標5/5. オプションの合成 opt size = Ls.command (opt (add_l empty)) let collect_files let files = in let rec iter cur_size acc files = match files with 5.オプションを合成可能 | [] -> acc にすること | file :: files -> let cur_size = cur_size + file..size in if cur_size > size then acc else iter cur_size (file..name :: acc) file in iter 0 [] files ;; collect_files add_t size collect_files add_S size 更新日時順 サイズ順 9 目標を実現する方針 • 1.コマンドの関数化 • 2.出力のレコード化 • DSLによるコマンドの 仕様の記述 • オプション集合をsingleton type で表す 出力行レコードに型レベル関数を 用いる • フィールドアクセスが安 全であることの証明を 生成 • オプション集合を タプルで表現 • 4.レコード型の柔軟な変化 • 3.フィールドの検査 • 5.オプションの合成 10 目標を実現する方針 • 1.コマンドの関数化 • 2.出力のレコード化 • DSLによるコマンドの 仕様の記述 • オプション集合をsingleton type で表す 出力行レコードに型レベル関数を 用いる • フィールドアクセスが安 全であることの証明を 生成 • オプション集合を タプルで表現 • 4.レコード型の柔軟な変化 • 3.フィールドの検査 • 5.オプションの合成 11 提案1/2:OCommand DSL/ 関数と型定義の導出 • コマンドの形式を記述する領域特化言語 (DSL) – オプションをコマンドに渡した時に,どのよう に形式が変化するかを記述 • コマンドの記述からコマンドをOCaml上で 型安全に扱うための関数,型定義を導出 12 DSLによる形式の定義 コマンドの名前 フィールドの出現順序 COMMAND ls : BEGIN ORDER {inode; owner; group; ...; name} OPTION i : ADD {inode : int} OPTION l : ADD {perm : perm; hard : int; ...} OPTION h : MODIFY {size : hsize} DEFAULT : {name : string} END オプションの作用 • オプションの作用:オプションが指定された時に出力 の形式がどう変化するか 13 DSLの生成コード • DSLで記述されたコマンドの仕様からコマ ンド関数の定義などが生成される module Ls = ... let empty let add_i let add_l let add_h struct = opt = opt = opt = ... ... ... ... let command opts = ... end オプション集合を操作 する関数の定義 コマンド関数 14 目標を実現する方針 • 1.コマンドの関数化 • 2.出力のレコード化 • DSLによるコマンドの仕 様の記述 • オプション集合をsingleton type で表す 出力行レコードに型レベル関数を 用いる • フィールドアクセスが安 全であることの証明を 生成 • オプション型をタプルで 表現 • 4.レコード型の柔軟な変化 • 3.フィールドの検査 • 5.オプションの合成 15 適切な型検査 • コマンド関数は引数のオプション集合に よって出力行レコード型が異なる # Ls.command empty - : {name : string} list 引数の値毎に異なる レコード型を返す # Ls.command (add_i empty) - : {inode : int; name : string} list # Ls.command (add_i (add_l empty)) - : {inode : int; perm : …; name : string} list 16 提案2/2:型レベル関数を用いた コマンド関数のエンコード • オプション集合をGADTsによって singleton typeにする • 出力行レコードに型レベル関数を用いる コマンド関数 : オプション集合型 一対一対応 (singleton type) → 出力行レコード型 型レベル関数 オプション集合値 依存 17 提案2/2:型レベル関数を用いた コマンド関数のエンコード • オプション集合をGADTsによって singleton typeにする • 出力行レコードに型レベル関数を用いる command : ('i opt_i * 'l opt_l * 'h opt_h) → (('i, 'l, 'h) perm * ... * ('i, 'l, 'h) size * ... * ('i, 'l, 'h) name)) list • レコードをタプルで表現 • perm, size, name等が型レベル関数 18 オプション指定型 • オプションが指定されているかどうかを 表す型 – GADTsを用いて定義 (* on, offは抽象型 *) type 'i opt_i = I_On : on opt_i | I_Off : off opt_i let let let let empty add_i add_l add_h = (I_Off, (_, l, h) (i, _, h) (i, l, _) L_Off, H_Off) = (I_On, l, h) = (i, L_On, h) = (i, l, H_On) 19 提案2/2:型レベル関数を用いた コマンド関数のエンコード • オプション集合をGADTsによって singleton typeにする • 出力行レコードに型レベル関数を用いる command : ('i opt_i * 'l opt_l * 'h opt_h) → (('i, 'l, 'h) perm * ... * ('i, 'l, 'h) size * ... * ('i, 'l, 'h) name)) list • レコードをタプルで表現 – perm, size, name等が型レベル関数 20 フィールド型 • 各オプション指定型からフィールド出現 型への型レベル関数 – フィールド出現型:フィールドが存在して その型がτであることを表す型τ preと フィールドが存在しないことを表すabs 'i 'l 'h size (off, on, off) = int pre size (off, on, on) = string pre size (off, off, off) = abs • OCamlは型レベル関数を直接サポートしていな いので何らかのエンコードが必要 21 フィールド型 type ('i, 'l, 'h) size = | Size : 'fld field * ('i, 'l, 'h, 'fld) rel_size 型引数'fldは存在量化されて外から隠されている 'fld fieldは'fldがpreの時のみ値を持つ型(GADTs) type 'fld field = | FPre : 'a -> 'a pre field | FAbs : abs field 22 フィールド型 type ('i, 'l, 'h) size = | Size : 'fld field * ('i, 'l, 'h, 'fld) rel_size • ∃x:(i, l, h, fld)rel_size ⇔ size(i,l,h)=fld type ('i, 'l, 'h, 'fld) rel_size = | Rel_size_0 : (off, off, off, abs) | Rel_size_1 : (off, off, on, abs) | Rel_size_2 : (off, on, off, int pre) | Rel_size_3 : (off, on, on, string pre) (* other 4 combinations of options *) rel_size rel_size 23 rel_size rel_size フィールド型 type ('i, 'l, 'h) size = | Size : 'fld field * ('i, 'l, 'h, 'fld) rel_size • x0 x0 • x1 x1 : = : = (off, off, off) size ⇒ (FAbs * Rel_size0) (off, on, off) size ⇒ (FPre (int型の値) * Rel_size2 型引数毎に'fldの型が変化している 24 目標を実現する方針 • 1.コマンドの関数化 • 2.出力のレコード化 • DSLによるコマンドの仕 様の記述 • オプション集合をsingleton type で表す 出力行レコードに型レベル関数を 用いる • フィールドアクセスが安 全であることの証明を生 成 • オプション集合を タプルで表現 • 4.レコード型の柔軟な変化 • 3.フィールドの検査 • 5.オプションの合成 25 フィールドアクセス≠パターンマッチ • 型を無視するとパターンマッチで取り出 せば良い file..size match file with | (..., Size (Fpre res, _), ...) -> res 存在量化されたresの型がエスケープするので 型エラー 26 フィールドアクセス=パターンマッチ+証明 • 存在型が具体的なフィールド出現型に等 しいことを証明して取り出す file..size (off, on, off, ?) rel_size -> (?, int pre) eq match file with | (..., Size (x, rel), ...) -> let eq = eq_rel_size_off_on_off rel in let FPre res = apply_eq eq x in res (?, int pre) eq -> ? field -> int pre field 27 実装 DSLで記述された コマンドの形式 コマンドを使う スクリプト ダミープログラム の生成(100行) コマンドモジュール の生成(600行) フィールド出現型 を証明する関数 型/関数定義 合計1000行程度の システムになった スクリプトの変換 (200行) 証明付きの スクリプト 型情報を得るための ダミーコード ocaml コンパイラ 型情報 (.annot) 28 事例研究 • lsコマンドの他に,df,psコマンドを本研 究が提案するDSLで扱えることを確かめた COMMAND df : BEGIN ORDER : {filesystem; type; size; used; available; use; mounted_on} ;; OPTION T : ADD {type : string};; OPTION h : MODIFY {size : hint; use : hint; avail : hint} ;; DEFAULT : {filesystem : string; size : int; used : int; available : int; use : int; mouted_on : string} END;; フィールド数:7 対応したオプション:T,h COMMAND ps : BEGIN ORDER : {f; s; uid; pid; ppid; c; pri; ni; addr; sz; wchan; stime; tty; time; cmd;} ;; OPTION e : ADD {} OPTION f : ADD {uid : string; ppid : int; c : int; stime : time} ;; OPTION l : ADD {f : int; s : string; uid : int; ppid : int; c : int; pri : int; ni : int; addr : string; sz : int; wchan : string} DEFAULT : {uid : int; tty : string; time : string; cmd : string with delim = ""} END;; フィールド数:15 対応したオプション:e,f,l 29 関連研究 : Caml-Shcaml [Heller, A. and Tov, J. A., ML2008] • 手法 – コマンドの形式をモジュールで記述 – モジュールをファンクターに渡すことでコマン ド関数等を生成 • 本研究との相違点 – オプションの合成等は扱っていない – 出力行レコードへのアクセスに対する型安全性 はない 30 課題: 多相なオプションへの対応 let sum_size opt size = = Ls.command (opt (add_l empty)) let files List.fold files ~init:0 ~f:(fun acc file -> acc + in file..size) ;; • この時, optには “lオプションと組み合わせた時の出力行レコードには sizeというint型のフィールドが存在する”ような任意の型 という型が付いてほしい • OCommandエンコードでは表現できない • 引数のオプションに対する制約はHaskellの type classと関数従属性を用いて表現できる 31 結論 • コマンドの仕様を記述し、コマンドを OCaml上で呼び出すためのコードを 生成するためのDSLを提案 – オプションを指定した時にどのように出力を 変化させるかを記述 • コンパイル時にフィールドアクセスが安全であ ることを検査するためのOCaml上での型の表現 を与えた – GADTsを用いて表現 • ls,ps,dfが扱えることを確かめた • 多相なオプションをどう扱うかは今後の課題 32
© Copyright 2025 ExpyDoc