Slide 1

Slide 1 text

形式手法を使って、
 発見しにくいバグを一網打尽にしよう
 1 Aug 30, builderscon tokyo 2019 鈴木 穂高 | Hodaka Suzuki システム本部品質統括部SWETグループ DeNA Co., Ltd.

Slide 2

Slide 2 text

自己紹介 ● 鈴木 穂高(すずき ほだか) ● 経歴 − 2014年DeNA新卒入社 − 2014年8月〜 大規模アプリゲーム開発 − 2018年10月〜 SWET − Androidテストチーム − テストの普及のための活動 − Game Testing Revolution(GTR)チーム − 仕様品質を向上させるための技術的なアプローチ研究 2

Slide 3

Slide 3 text

自己紹介 ● 鈴木 穂高(すずき ほだか) ● 経歴 − 2014年DeNA新卒入社 − 2014年8月〜 大規模アプリゲーム開発 − 2018年10月〜 SWET − Androidテストチーム − テストの普及のための活動 − Game Testing Revolution(GTR)チーム − 仕様品質を向上させるための技術的なアプローチ研究 3

Slide 4

Slide 4 text

お話すること ● 動機 ● 形式手法とは ● 私が考える適用可能性 4 Main CFP: https://builderscon.io/builderscon/tokyo/2019/session/e14fce7f-7ccc-42e5-b240-062e8719fa83

Slide 5

Slide 5 text

動機 5

Slide 6

Slide 6 text

動機 6 ゲーム開発をしているときに多かった 仕様の不備(考慮漏れ、記載漏れ、矛盾など)に 開発フェーズの早い段階で気づきたい。

Slide 7

Slide 7 text

7 一般的な開発フロー 企画 実装 QA リリース 仕様作成 仕様不備の発見が後になればなるほど、 修正を伴う手戻り工数は大きくなる傾向がある。 小 大 手戻り工数

Slide 8

Slide 8 text

8 開発チームも、レビュー、チェックリスト運用などで 対応していたが、技術的なアプローチで何かできないか 探していたところ、形式手法に出会った。

Slide 9

Slide 9 text

形式手法とは 9

Slide 10

Slide 10 text

形式手法 仕様を明確に記述したり、記述された設計の性質を機 械的に検証する手法の総称。 形式手法にもいくつか種類があるが、 いずれも数学に基づく科学的な裏付けを持つ。 登場は古く、1970年代頃から。 10

Slide 11

Slide 11 text

形式手法にも種類が色々ある 11 種類 説明 代表的な記述言語 形式仕様記述 矛盾がなく論理的に正しい仕様を作 成する VDM++/Event-B/ Z/Alloy etc. モデル検査 プログラムの状態をモデル化すること で、プログラムが期待される性質を満 たすことを検証する Promela/TLA+ etc. 他もありますが、今回は割愛。

Slide 12

Slide 12 text

簡単に言うと、こんなことができる 日本語の仕様書や、プログラムを 専用の記述言語で書き表し、ツールにかませると システマチックに不備を発見できる 12 形式手法

Slide 13

Slide 13 text

モデル検査の例 1. 検査したいもの(仕様書、ソースコードなど) から専用言語でモデルを作成する 2. 検査対象が満たすべき性質から検査式を作成する 3. モデル検査ツールにかける 13

Slide 14

Slide 14 text

byte n = 0; active proctype P() { n = 1; printf(“process P, n = %d\n”, n) } active proctype Q() { n = 2; printf(“process Q, n = %d\n”, n) } 14 「Spinモデル検査入門」より引用 ・・・① ・・・② ・・・③ ・・・④ モデル検査の例(Spin/Promela) 「異なるプロセスP, Qが、globalなnへの代入とprintを 実行する」をモデル化

Slide 15

Slide 15 text

byte n = 0; active proctype P() { n = 1; assert(n == 1); printf(“process P, n = %d\n”, n) } active proctype Q() { n = 2; printf(“process Q, n = %d\n”, n) } 15 モデル検査の例 assertを入れ、Spinの状態空間で成り立つことを 確認する

Slide 16

Slide 16 text

モデル検査の例 システムが取りうる状態・パスを自動で網羅的に 探索する 16 1 2 3 4 5 6 ① n = 1 ① n = 1 ① n = 1 ③ n = 2 ③ n = 2 ③ n = 2 ② printf(P) ③ n = 2 ③ n = 2 ④ printf(Q) ① n = 1 ① n = 1 ③ n = 2 ② printf(P) ④ printf(Q) ① n = 1 ④ printf(Q) ② printf(P) ④ printf(Q) ④ printf(Q) ② printf(P) ② printf(P) ② printf(P) ④ printf(Q)

Slide 17

Slide 17 text

$ spin -a -o2 sample.pml $ gcc -o pan pan.c $ ./pan -e pan:1: assertion violated (n==1) (at depth 4) pan: wrote sample.pml1.trail pan: wrote sample.pml2.trail ... 17 モデル検査の例 ツールをかませる

Slide 18

Slide 18 text

$ spin -a -o2 sample.pml $ gcc -o pan pan.c $ ./pan -e pan:1: assertion violated (n==1) (at depth 4) pan: wrote sample.pml1.trail pan: wrote sample.pml2.trail ... 18 モデル検査の例 assertionが満たされないパスが2つあることが わかる

Slide 19

Slide 19 text

$ spin -pglsr -t1 sample.pml using statement merging 1: proc 0 (P:1) sample.pml:4 (state 1) [n = 1] 2: proc 1 (Q:1) sample.pml:10 (state 1) [n = 2] process Q, n = 2 3: proc 1 (Q:1) sample.pml:11 (state 2) [printf('process Q, n = %d\\n',n)] 4: proc 1 terminates spin: sample.pml:5, Error: assertion violated spin: text of failed assertion: assert((n==1)) 5: proc 0 (P:1) sample.pml:5 (state 2) [assert((n==1))] spin: trail ends after 5 steps #processes: 1 n = 2 5: proc 0 (P:1) sample.pml:6 (state 3) 2 processes created 19 モデル検査の例 反例が出たときの計算の再構成に必要なデータ (trailファイル)をベースに再度シミュレーション

Slide 20

Slide 20 text

$ spin -pglsr -t1 sample.pml using statement merging 1: proc 0 (P:1) sample.pml:4 (state 1) [n = 1] 2: proc 1 (Q:1) sample.pml:10 (state 1) [n = 2] process Q, n = 2 3: proc 1 (Q:1) sample.pml:11 (state 2) [printf('process Q, n = %d\\n',n)] 4: proc 1 terminates spin: sample.pml:5, Error: assertion violated spin: text of failed assertion: assert((n==1)) 5: proc 0 (P:1) sample.pml:5 (state 2) [assert((n==1))] spin: trail ends after 5 steps #processes: 1 n = 2 5: proc 0 (P:1) sample.pml:6 (state 3) 2 processes created 20 1つ目の反例

Slide 21

Slide 21 text

$ spin -pglsr -t2 sample.pml using statement merging 1: proc 0 (P:1) sample.pml:4 (state 1) [n = 1] 2: proc 1 (Q:1) sample.pml:10 (state 1) [n = 2] spin: sample.pml:5, Error: assertion violated spin: text of failed assertion: assert((n==1)) 3: proc 0 (P:1) sample.pml:5 (state 2) [assert((n==1))] spin: trail ends after 3 steps #processes: 2 n = 2 3: proc 1 (Q:1) sample.pml:11 (state 2) 3: proc 0 (P:1) sample.pml:6 (state 3) 2 processes created 21 2つ目の反例

Slide 22

Slide 22 text

モデル検査の例 2つの反例が見つかった 22 1 2 3 4 5 6 ① n = 1 ① n = 1 ① n = 1 ③ n = 2 ③ n = 2 ③ n = 2 ② printf(P) ③ n = 2 ③ n = 2 ④ printf(Q) ① n = 1 ① n = 1 ③ n = 2 ② printf(P) ④ printf(Q) ① n = 1 ④ printf(Q) ② printf(P) ④ printf(Q) ④ printf(Q) ② printf(P) ② printf(P) ② printf(P) ④ printf(Q) assert(n==1)

Slide 23

Slide 23 text

さきほどのは至極簡単なモデル検査の例だが、 形式手法を適用して品質向上につながったという事例 は、欧米を中心に多く報告されている。 有名どころだと、AWSでも利用されている。 日本でもFelica等で利用されている。 23

Slide 24

Slide 24 text

私のこれまでの道筋 ● 形式手法についてキャッチアップ − 形式手法について調べてみた ● プロダクトに適用できそうかPoCを作成して確認 − SWETグループが考える形式手法の現在とこれからの可能 性 24

Slide 25

Slide 25 text

よし、プロジェクト導入だ! 25

Slide 26

Slide 26 text

とはならない! 26

Slide 27

Slide 27 text

● うちの会社で使ったときの効果は? ● 学習コストは? ● 運用コストは? ● 人どれだけ雇う必要があるの? ● などなど 27 会社で導入するためには...

Slide 28

Slide 28 text

私のこれまでの道筋 ● 形式手法についてキャッチアップ − 形式手法について調べてみた ● プロダクトに適用できそうかPoCを作成して確認 − SWETグループが考える形式手法の現在とこれからの可能 性 ● 実際のプロダクトに適用して、会社として投資 できそうかを確かめる 28 Now

Slide 29

Slide 29 text

私が考える適用可能性 29

Slide 30

Slide 30 text

実際のプロダクトに適用する ● 対象のプロダクト − Oyakata − DeNAで現在内製開発している、ゲームに特化した マスターデータの編集・管理ツール − https://genom.dena.com/event/20190327_study/ 30

Slide 31

Slide 31 text

やったこと ● 形式仕様記述とモデル検査を適用 − 適用中に記録すること − どれくらいの不備が見つかったのか − 不備のインパクトはどれほどか − その他、かかった時間、感想など 31

Slide 32

Slide 32 text

32 フェーズとしては、αテストの最中 SWET Oyakata dev Team ● エンジニアOnlyで構成 ● プロダクト要求仕様書( PRD)も エンジニアが記述 ゲーム Team フィードバック プロダクト提供 PRDに対して形式仕様記述 コードに対してモデル検査 Oyakata含めた開発体制

Slide 33

Slide 33 text

改めて目的 33 ● 投資判断材料を集める − 「Oyakataチームに貢献」よりも、まずは技術を もっと知る

Slide 34

Slide 34 text

形式仕様記述 34

Slide 35

Slide 35 text

形式仕様記述 ● 既存でいくつかあるプロダクト要求仕様書 (PRD)を書き起こす − Alloy 4.2 − http://alloytools.org − 「関係」を使って空間や時間の構造を表現する 35

Slide 36

Slide 36 text

例 ● 各ユーザーはロールを持つ ● ロールには、「一般」と「Admin」がある ※説明のため実際の仕様書に記載されていた内容を改変しています 36

Slide 37

Slide 37 text

例 ● 各ユーザーはロールを持つ ● ロールには、「一般」と「Admin」がある 37 User Role

Slide 38

Slide 38 text

例 38 ● 各ユーザーはロールを持つ ● ロールには、「一般」と「Admin」がある 集合で表現 User Role 一般 Admin 1ユーザーの 集まり

Slide 39

Slide 39 text

例 39 関係を記述する ● 各ユーザーはロールを持つ ● ロールには、「一般」と「Admin」がある User Role

Slide 40

Slide 40 text

例 40 ● 各ユーザーはロールを持つ ● ロールには、「一般」と「Admin」がある User Role

Slide 41

Slide 41 text

例 41 ● 各ユーザーはロールを持つ ● ロールには、「一般」と「Admin」がある User Role

Slide 42

Slide 42 text

例 42 ● 各ユーザーはロールを持つ ● ロールには、「一般」と「Admin」がある User Role

Slide 43

Slide 43 text

例 43 色々な関係が考えられる ● 各ユーザーはロールを持つ ● ロールには、「一般」と「Admin」がある User Role

Slide 44

Slide 44 text

例 44 ● 各ユーザーはロールを持つ ● ロールには、「一般」と「Admin」がある ● 1人のメンバーは1つのロールが割り当てられる ● 1つの役職を複数メンバーが担当することができる ● AdminのみOyakata上から0人にすることは できない

Slide 45

Slide 45 text

形式仕様記述 abstract sig Role {} one sig General, AD extends Role {} sig User {} sig Oyakata { role: User -> one Role, } 45

Slide 46

Slide 46 text

形式仕様記述 abstract sig Role {} one sig General, AD extends Role {} sig UserId {} sig Oyakata { role: User -> one Role, } 46 sig : 要素(アトム)の集合を表す

Slide 47

Slide 47 text

形式仕様記述 abstract sig Role {} one sig General, AD extends Role {} sig User {} sig Oser { role: UserId -> one Role, } 47 Role General Admin Admin要素1 General 要素1

Slide 48

Slide 48 text

形式仕様記述 abstract sig Role {} one sig General, AD extends Role {} sig User {} sig User { role: UserId -> one Role, frozen: UserId -> one FreezeStatus } 48 User User 要素1 User 要素2 User 要素N ・・・

Slide 49

Slide 49 text

形式仕様記述 abstract sig Role {} one sig General, AD extends Role {} sig UserId {} sig Oyakata { role: User -> one Role, } 49 User Role Admin General

Slide 50

Slide 50 text

形式仕様記述 abstract sig Role {} one sig General, AD extends Role {} sig UserId {} sig Oyakata { role: User -> one Role, } 50 User Role Admin Oyakata role General 関係を表す

Slide 51

Slide 51 text

形式仕様記述 続き fact AtLeastAdmin { // 「Adminの役職をOyakata上から // 0人にすることはできない」を不変条件としてセット all o: Oyakata | AD in User.(o.role) } 51

Slide 52

Slide 52 text

形式仕様記述(実際の仕様) ● Adminは次のことができる − ユーザーの凍結 − 凍結したユーザーではログインできなくなる − 情報表示の不整合が起こるので、ユーザー削除はできな い 52

Slide 53

Slide 53 text

形式仕様記述 abstract sig FreezeStatus {} one sig Frozen, NotFrozen extends FreezeStatus {} abstract sig Role {} one sig General, AD extends Role {} sig User {} sig Oyakata { role: User -> one Role, frozen: User -> one FreezeStatus } 53

Slide 54

Slide 54 text

形式仕様記述 abstract sig FreezeStatus {} one sig Frozen, NotFrozen extends FreezeStatus {} abstract sig Role {} one sig General, AD extends Role {} sig UserId {} sig User { role: UserId -> one Role, frozen: UserId -> one FreezeStatus } 54 FreezeStatus Frozen NotFrozen Frozen要素1 NotFrozen要素1

Slide 55

Slide 55 text

形式仕様記述 abstract sig FreezeStatus {} one sig Frozen, NotFrozen extends FreezeStatus {} abstract sig Role {} one sig General, AD extends Role {} sig UserId {} sig Oyakata { role: User -> one Role, frozen: User -> one FreezeStatus } 55 User FreezeStatus NotFrozen Frozen

Slide 56

Slide 56 text

形式仕様記述 abstract sig FreezeStatus {} one sig Frozen, NotFrozen extends FreezeStatus {} abstract sig Role {} one sig General, AD extends Role {} sig UserId {} sig Oyakata { role: User -> one Role, frozen: User -> one FreezeStatus } 56 User FreezeStatus NotFrozen Oyakata frozen Frozen

Slide 57

Slide 57 text

形式仕様記述 続き pred freeze(o, o': Oyakata, u: User) { o'.role = o.role (u -> NotFrozen) in o.frozen o'.frozen = o.frozen ++ (u -> Frozen) } 57 形式仕様記述

Slide 58

Slide 58 text

形式仕様記述 続き pred freeze(u, u': User, uid: UserId) { u'.role = u.role (uid -> NotFrozen) in u.frozen u'.frozen = u.frozen ++ (uid -> Frozen) } 58 形式仕様記述 User FreezeStatus Oyakata o o’ NotFrozen Frozen frozen

Slide 59

Slide 59 text

形式仕様記述 続き pred freeze(u, u': User, uid: UserId) { u'.role = u.role (uid -> NotFrozen) in u.frozen u'.frozen = u.frozen ++ (uid -> Frozen) } 59 形式仕様記述 User FreezeStatus Oyakata o o’ NotFrozen Frozen frozen

Slide 60

Slide 60 text

形式仕様記述 続き pred freeze(u, u': User, uid: UserId) { u'.role = u.role (uid -> NotFrozen) in u.frozen u'.frozen = u.frozen ++ (uid -> Frozen) } 60 形式仕様記述 User FreezeStatus Oyakata o o’ NotFrozen Frozen frozen

Slide 61

Slide 61 text

形式仕様記述 続き run freeze for 3 but exactly 2 Oyakata 61 形式仕様記述

Slide 62

Slide 62 text

結果(いくつかあるうちの1つを表示) 62 freeze前 o

Slide 63

Slide 63 text

結果(いくつかあるうちの1つを表示) 63 freeze前 freeze後 o o’

Slide 64

Slide 64 text

結果(いくつかあるうちの1つを表示) 64 「全管理者が凍結状態」という状態が浮かび上がる freeze前 freeze後 o o’

Slide 65

Slide 65 text

● テーブルの仕組み ● ブランチの仕組み ● スキーマの仕組み ● など... 65 こんな形で他のPRDも形式仕様記述で書く

Slide 66

Slide 66 text

結果 PRD15枚読み、13の不備を指摘。 うち、重要度が高いものは3件。 66

Slide 67

Slide 67 text

やってみてわかったこと ● 自然言語で書かれている仕様にバグはほとんどな かった − 書かれていない仕様にバグが集中している ● モデリングに苦戦することが多い ● 結果を見て気付くパターンよりも、形式仕様記述を書 いている最中に仕様の不備に気づくパターンの方が 多い − 一応これも形式仕様記述で言われている効果ではある 67

Slide 68

Slide 68 text

やってみてわかったこと 68 ● Alloyの文法自体がチェックリストになっている − Alloyでは関係を厳密に定義していく必要がある − 日本語の仕様に曖昧な部分があれば、Alloyで 書き起こすことを通して気づくことができる ● 結論ありきなモデリングになってしまうことが多々 ● αテストのフェーズで形式仕様記述を 適用するのはやはり遅い ● 仕様をインクリメンタルで書いていく形が向いている気 がした

Slide 69

Slide 69 text

今後検討していくこと ● 運用を見越すと、チームとして仕様の変更に 強くなれるかどうか ● 今の記述の仕方に問題がある可能性がないか − 実践経験のある人に勘所を教えてもらう必要がある ● Alloyよりももっと適切なツールがあるかどうか 69

Slide 70

Slide 70 text

モデル検査 70

Slide 71

Slide 71 text

モデル検査 ● Oyakataのプログラムで、考慮漏れが発生しそうな 部分を見つけ、モデル検査を適用してみる − Spin Version 6.4.8 − http://spinroot.com/spin/whatispin.html 71

Slide 72

Slide 72 text

モデル検査対象 ● Oyakataではnomsを使っている − https://github.com/attic-labs/noms − nomsを簡単に説明すると、 「バージョン管理ができるデータベース」 − Go言語で書かれている ● nomsに対してモデル検査を適用する − nomsの実装を読み、それをうまいことモデリング していく 72

Slide 73

Slide 73 text

nomsをモデリングしてみた結果...「挫折」 ● 原因 − Promelaの表現力不足 − 高階関数を扱えない Promela で高階関数が多用された コードを表現するのが大変 − nomsの設計がモデル検査に向いていない − 直感的でない抽象をもつので、簡易的なモデル作成の決 断を下せない − モデル対象の関数が5つ6つほどスタックを積むので モデル化の範囲が広すぎる 73

Slide 74

Slide 74 text

方針転換 ● GoのプログラムをPromelaに機械的に 変換できないか − 将来的に自動生成みたいなことが できれば、モデリングのコストも減る 74

Slide 75

Slide 75 text

Go -> Promela変換の手引き書の作成 75 func Example() { // do something } 例1: 関数宣言(Go)

Slide 76

Slide 76 text

Go -> Promela変換の手引き書の作成 76 proctype Example() { bool panic = false // do something _panic: panic = true goto _defer _return: _defer: // NOTE: 関数の終了を明示する。シミュレーションランの際に // panic したのかどうかをわかりやすくするために // printf を入れている。 end: printf("Example: panic=%d\n", panic) } 例1: 関数宣言(Promela)

Slide 77

Slide 77 text

Go -> Promela変換の手引き書の作成 77 func Example() { // do something panic(err) } 例2: panic関数(Go)

Slide 78

Slide 78 text

Go -> Promela変換の手引き書の作成 例2: panic関数(Promela) 78 proctype Example() { bool panic = false // do something // NOTE: panic は goto を使う。defer もちゃんと実行される。 goto _panic _panic: panic = true goto _defer _return: _defer: end: printf("Example: panic=%d\n", panic) }

Slide 79

Slide 79 text

Goでよくあるバグを検出してみる ● Understanding Real-World Concurrency Bugs in Go − https://songlh.github.io/paper/go-study.pdf 79

Slide 80

Slide 80 text

Goでよくあるバグを検出してみる wg := &sync.WaitGroup{} wg.Add(5) for i := 0; i < 5; i++ { go func() { defer wg.Done() }() wg.Wait() } 80 Understanding Real-World Concurrency Bugs in Go

Slide 81

Slide 81 text

Goでよくあるバグを検出してみる wg := &sync.WaitGroup{} wg.Add(5) for i := 0; i < 5; i++ { go func() { defer wg.Done() }() wg.Wait() } 81 Understanding Real-World Concurrency Bugs in Go Waitでブロックしてしまうので、本来はforの外に出さないといけない

Slide 82

Slide 82 text

手続きに従って変換をかける 82 Promelaコードが長いので略

Slide 83

Slide 83 text

検査をしてみる $ spin -a -o2 ./main.pml $ gcc -DREACH -o ./pan ./pan.c $ ./pan -c0 -e pan:1: invalid end state (at depth 17) pan: wrote main.pml1.trail (以降略) 83

Slide 84

Slide 84 text

検査をしてみる % spin -t1 main.pml S →E spin: trail ends after 18 steps #processes: 2 rwmutexMaxReaders = 16384 _prev_golang_chan = -1 _golang_chans[0].closed = 0 (中略) _golang_chans[9].closed = 0 group.count = 2 18: proc 1 (main:1) ./../../promela-go/golang.pml:309 (state 19) 18: proc 0 (:init::1) main.pml:65 (state 5) 3 processes created 84

Slide 85

Slide 85 text

検査をしてみる 309| inline sync_WaitGroup_Wait(wg) { 310| wg.count == 0 311| } 85 WaitGroup.Wait から帰ってこない状況がありうることがわかる

Slide 86

Slide 86 text

やってみてわかったこと 86 ● 大きなプログラムをモデリングするのは難しい − プログラムを表現の幅と、モデリング言語の表現の幅が違う ため ● モデリング自体は難しいが、 ライブラリとして提供するという手法は 継続してやっていく価値はありそう

Slide 87

Slide 87 text

今後検討していくこと 87 ● モデル検査の利用法 − 設計が正しいかどうかを確認する − こちらは個人利用にとどまる予感 − 書いたプログラムを(部分的に)静的解析する − プロジェクトで運用することを見据えるならば こちらができると嬉しい ● Promelaよりももっと適切なツールがあるかどうか

Slide 88

Slide 88 text

まとめ 88 ● 形式仕様記述/モデル検査を、運用込みで プロダクトに適用できるかどうかを見定めている ● まだまだ道半ば