Slide 1

Slide 1 text

Defense Against Undefined Behavior 未定義動作に対する防衛術 発表をご覧になった方々へのお詫び: Sequence Point・Sequence-beforeの解説内容に不備がありました。 間違いの訂正に関して赤字で修正・加筆を行っています。 クイズの解説スライドも追加しているのでご査収下さい。 Sequence PointはC++03までのルールでした。 C++11からはSequence-beforeで説明されますです。 大変申し訳ありません。

Slide 2

Slide 2 text

Rust 1.31.0 RELEASE!! Rust 2018 Edition Stable!! 使っていきましょう!! Non-lexical lifetimes!!

Slide 3

Slide 3 text

自己紹介  神:いなむのみたま@mitama_rs  CADDi株式会社に雇用されている  CADを解析にC++17を使っている  ラーメンが好き  けん玉が得意

Slide 4

Slide 4 text

Undefined Behavior 101 未定義動作入門  Defined Behavior  規格書が明示的に一つの動作を定めている  Implementation Defined Behavior  複数の動作からコンパイラが動作を選べる  どの動作をするかドキュメントに明記されなければならない  Unspecified Behavior  複数の動作のどの動作をしても良いことになっている  Undefined Behavior(UB)  完全に何も決まっていない  何が起こってもおかしくない

Slide 5

Slide 5 text

Why Study Undefined Behavior? なぜ未定義動作を学ぶのか?  UBはプログラム全体を未定義にする  UBが起こったときにはコンパイラは何をしてもいいことに起因する  例えば、UBを放置した結果6ヶ月後にレアケースでバグる  または、ツールチェインをかえたらエラーになって動かないなど  UBによるバグの修正には時間を要しがち  髪の毛も抜ける  髪の毛を守るためにUBを避ける技術を学ぼう!

Slide 6

Slide 6 text

Defined Undefined Behavior 未定義動作だと定義されているもの  ヌルポインタのデリファレンス  配列のレンジ外アクセス  未初期化の変数の使用  異なるポインタ型を介したオブジェクトへのアクセス(Strict Aliasing)  デストラクト済みの変数へのアクセス  副作用のない無限ループ  レースコンディション  整数型のサイズを超えたシフト演算  コンストラクタ・デストラクタからの純粋仮想関数の呼び出し  整数型のゼロ割り  整数型のオーバーフロー  他多数. . .

Slide 7

Slide 7 text

Really easy explanation of UB よく分かる未定義動作の解説 C++ 料理 →

Slide 8

Slide 8 text

Cooking and UB 料理と未定義動作  例えば塩と砂糖を間違えると…  料理全体が未定義に!  C++も同じ  規格書が定めていることを書くことが重要  規格書が定めていないものはすべて未定義

Slide 9

Slide 9 text

Recipe and Standard レシピと規格書  料理はレシピが規定し、C++プログラムは規格書が規定する 材料 完成品 レシピ コード 挙動 規格書

Slide 10

Slide 10 text

Quiz 突然ですがクイズです varA = 5; varA = ++varA + 2; ↑varA = ??? varB = 3; varB = varB++ + 2; ↑varB = ???

Slide 11

Slide 11 text

Correct answer 正解はこちら varA = 5; varA = ++varA + 2; // C++03, undefined behavior varA == 8; // C++11, defined varB = 3; varB = varB++ + 2; // C++03, undefined behavior varB == 5; // C++11, undefined behavior // C++17, defined

Slide 12

Slide 12 text

Side Effects, Sequence and UB 副作用とシーケンスと未定義動作  同一のオブジェクトに対する2つの副作用があり、  それらの処理順が定められていない場合、  未定義動作になる  (§1.9.15, C++11 / §4.6.17, C++17) x++ + ++x; // crazy expression

Slide 13

Slide 13 text

Side Effects 副作用  式がある状態(変数など)を変更すると、副作用が生じる  雑に言うと、副作用は値を返す以外の何かである

Slide 14

Slide 14 text

What happened with C++11 and C++17? C++11とC++17で何が変わった?  C++11:Squence point rules(C++03)の終わり, Sequence-before rules(C++11)の誕生  C++17: Sequence-before rulesの大幅な追加  ※C++11でSequence point rules自体の刷新が行われていました

Slide 15

Slide 15 text

Sequence point rules (until C++11)  すべての式の副作用が完了し  次の式が評価されていないコード内の場所  通常は式の最後  full-expressionの途中はよくわからん状態  C++03のルール、C++11からはSequence-before rules

Slide 16

Slide 16 text

Sequence-before and Sequence-after (from C++11) 副作用完了点の定義 なんらかの式AとBがあるとき  式Aは式Bの前にシーケンスすることができ、これは式Bが式Aの後にシーケンス されることと同じである  不確定なシーケンスは、ある式が他の式の前にシーケンスされているところで 発生し、最初に起こるものは不明である  AがBより前にシーケンスされず、BがAより前にシーケンスされない場合、式A およびBの評価はシーケンスされない(重複し得る)

Slide 17

Slide 17 text

In other words つまり  C++11以降では  ある式に式Aと式Bが含まれているとき  AとBの評価順が決まっているものと  そうでないものがある  一意に評価順が定まらない場合  AとBに同一のオブジェクトへの副作用が含まれていたら未定義動作になる  Unsequencedと呼ばれる状態

Slide 18

Slide 18 text

C++11:諸々の評価順序の規定  あらゆるオペレーターのオペランドの値の評価(ただし副作用を除く) はオペ レーターの結果の評価より先に評価される(ただし副作用を除く)  A + B という式ではAとBが評価されてから結果が評価される  built-in assignmentがSequence-before rulesで順序規定された  A = B という式では式Bの評価(副作用を除く)が完了してから代入される  Built-in 前置++(--)の副作用はSequence-before rulesで順序規定された  ++AがA = A + 1に置き換えられるという事実による暗黙のルール  上記2つの順序規定より自明  ++Aの副作用は++Aの評価のあと完了する!  副作用の評価全然定まってねえ  式に副作用が2つあったとき、たいてい未定義の国へ直行

Slide 19

Slide 19 text

C++17:諸々の副作用の評価順序規定  In a subscript expression E1[E2], every value computation and side-effect of E1 is sequenced before every value computation and side effect of E2  In a shift operator expression E1<>E2, every value computation and side-effect of E1 is sequenced before every value computation and side effect of E2  In every simple assignment expression E1=E2 and every compound assignment expression E1@=E2, every value computation and side-effect of E2 is sequenced before every value computation and side effect of E1  etc...  いい感じに副作用が完了するようになった!

Slide 20

Slide 20 text

C++17:関数の引数評価順序  Until C++17: unspecified  C++17:引数の評価は、関数呼び出しの前に「順序付けされる」 f(g(h(var)));  関数に引数は呼び出し前に評価されるので  fより前にg、gより前にhが評価される  fの評価時にはgの副作用が完了しており  gの評価時にはhの副作用が完了している

Slide 21

Slide 21 text

ただし、引数の評価順序はunspecified int var; func( var = hoge(), huga(var) ); // ??? huga(var); var = hoge(); func(); var = hoge(); huga(var1); func(); どちらにもなりえる

Slide 22

Slide 22 text

さきほどのクイズのポイント  同一変数の副作用の評価にSequence-before ruleが成立するか?

Slide 23

Slide 23 text

さきほどのクイズ解説 varB = 3; varB = varB++ + 2; C++11 1. ++varAの副作用はvarAの評価の後評価される 2. ++varA + 2 は結果より先にオペランドが評価される 3. varA = ++varA + 2 は ++varA + 2 が先に評価される 4. 代入で副作用が発生するが、 1. 2. 3. により代入前にインクリメントによる副作用 が終了する 5. varAの副作用はすべてsequenced 6. よってDefined Behavior Q.E.D.

Slide 24

Slide 24 text

さきほどのクイズ解説 varA = 5; varA = ++varA + 2; C++11 1. varA++の結果はその副作用よりも前に評価される 2. varA++ + 2 は結果より先にオペランドが評価される(副作用を除く) 3. varA = varA++ + 2 は varA++ + 2 が先に評価される(副作用を除く) 4. 代入で副作用が発生するが、varA++の副作用が代入前に完了確定していない 5. varAの副作用がunsequenced 6. よって、Undefined Behavior Q.E.D.

Slide 25

Slide 25 text

さきほどのクイズ解説 varA = 5; varA = ++varA + 2; C++17 1. varA++の結果はその副作用よりも前に評価される 2. varA++ + 2 は結果より先にオペランドが評価される(副作用を除く) 3. varA = varA++ + 2 は varA++ + 2 が先に評価され、 varA++ + 2 の副作用はすべて完了する 4. 代入で副作用が発生するが、 1. 2. 3. により代入前にインクリメントによる 副作用が終了する 5. varAの副作用はすべてsequenced 6. よって、Defined Behavior Q.E.D.

Slide 26

Slide 26 text

Avoiding Undefined Behavior 未定義動作を避けるために

Slide 27

Slide 27 text

General 基本だけど重要なこと  規格書を読んだり  cppreference.comやcpprefjpのようなサイトを読んだり  コードレビューをしたり  コーナーケースでテストしたり  静的解析ツールを使ったり  コンパイラの警告耳を傾ける  -Wall –Wextra (-pedantic) とにかく、これらをサボらないこと

Slide 28

Slide 28 text

Using Sanitizers サニタイザーの利用  clang  Address Sanitizer  Memory Sanitizer  Undefined Behavior Sanitizer  Thread Sanitizer  gcc  Address Sanitizer  Undefined Behavior Sanitizer

Slide 29

Slide 29 text

Try Multiple Compilers 複数のコンパイラの利用  処理系によってUBが発生する可能性がある  複数のコンパイラでの確認は有効  新しいコンパイラほど賢い  一度最新のコンパイラでコンパイルしてみる価値はある

Slide 30

Slide 30 text

Undefined Behavior C++17 Compiler Warnings C++17対応コンパイラの未定義動作の警告  GCC 7.3, GCC 8.2  operation on 'varB' may be undefined [-Wsequence-point]  clang 6.0, clang 7.0  unsequenced modification and access to 'varB' [-Wunsequenced]

Slide 31

Slide 31 text

Using Online Compilers オンラインコンパイラの利用  WandboxやCompiler Explorerなど  複数のコンパイラで気軽にコンパイルできる!  いろいろなエラーが見られて楽しい!  最新の機能を試せる!

Slide 32

Slide 32 text

Try –Wlifetime on Compiler Explorer Compiler Explorerで-Wlifetimeを試す

Slide 33

Slide 33 text

Wandboxを使ってCIを回してみた  あー全部のコンパイラでテスト回すの面倒だなー  WandboxにAPIあるじゃん!?  テストコードをWandboxに送ってコンパイル&実行  Wandboxの間違った使い方かもしれん

Slide 34

Slide 34 text

CBT  C++コードをinclude解析し  まとめてWandboxに送り  実行結果を表示できる  CLI  これを使ってテストを送りつけるぞ!

Slide 35

Slide 35 text

Conclusion まとめ  UBが起こったあとのプログラムはすべてが未定義と思え  私はこの状態を未定義の国のアリスと呼んでいる  規格書に明記されてないものはすべて未定義  未定義動作をクリティカルなバグとして扱いましょう

Slide 36

Slide 36 text

References 参考資料  https://www.copperspice.com/pdf/Undefined-Behavior-CppCon-2018.pdf  ISO/IEC 14882:2011 Programming Languages -- C++  ISO/IEC 14882:2017 Programming Languages -- C++  http://d.hatena.ne.jp/yohhoy/20170104/p1(yohhoyの日記)  ついでにお世話になったツール・サイト  Compiler Explorer(https://godbolt.org/)  Radare2(https://github.com/radare/radare2)

Slide 37

Slide 37 text

補足 Q&A

Slide 38

Slide 38 text

UBサニタイザしたら安心できる? そもそも何してるの?  UBサニタイザが通したからといって信用は禁物  UBサニタイザは大量の簡単なヒューリック

Slide 39

Slide 39 text

逆アセしてみる  サニタイザが何してるのか気になったので逆アセしてみた  まずサニタイザに引っかかります

Slide 40

Slide 40 text

radare2でバイナリを解析する  https://github.com/radare/radare2 バイナリをr2でaa(analyze all)して ヒントアドレス付近を逆アセで確認。 ひたすらチェックが差し込まれてる。 このようなコードがいたるところに。 これがUBSanがやっていること。 年々賢くなっているとはいえ、 チェック漏れもあるかも?