ハクソク

世界を動かす技術を、日本語で。

Fil-Cの簡易モデル

概要

  • Fil-CはC/C++のメモリ安全性を実現するための実装
  • ソースコードを書き換え、各ポインタにAllocationRecord*を付随
  • メモリ管理に**ガーベジコレクタ(GC)**を導入
  • 標準関数もFil-Cバージョンに自動変換
  • パフォーマンス低下と引き換えに、安全性向上

Fil-C: C/C++のメモリ安全実装の仕組み

  • Fil-CはC/C++コードを自動で書き換え、メモリ安全性を確保
  • 本来はLLVM IRを書き換えるが、簡易版ではC/C++ソースコードを書き換え
  • 各関数内のポインタ型ローカル変数ごとにAllocationRecord*変数を追加
    • 例: T1* p1;T1* p1; AllocationRecord* p1ar = NULL;
  • AllocationRecord構造体は、可視バイト列・不可視バイト列・長さを管理
  • ポインタ間の代入や演算時、AllocationRecord*も一緒に移動
  • ポインタを引数や戻り値として渡す際も、AllocationRecord*をセットで扱う
  • 標準ライブラリ関数(例: malloc, free)もFil-Cバージョンに置き換え
    • 例: {p1, p1ar} = filc_malloc(x); ... filc_free(p1, p1ar);

filc_mallocの仕組み

  • filc_mallocは3つのメモリアロケーションを行う
    • AllocationRecord本体
    • visible_bytes(実際のデータ領域)
    • invisible_bytes(ポインタのメタデータ領域)
  • invisible_bytesはAllocationRecord*型の配列として機能

ポインタのデリファレンスとバウンドチェック

  • ポインタ参照時、AllocationRecord*で境界チェックを実施
    • NULLチェック、範囲チェック、型サイズチェック
  • ポインタ値自体のロード/ストア時には、invisible_bytesで対応するAllocationRecord*もロード/ストア

filc_freeの動作

  • filc_freeはvisible_bytesとinvisible_bytesのみ解放
  • AllocationRecord本体はGCによって後で解放

ガーベジコレクタ(GC)の役割

  • 到達不能なAllocationRecordを探索し、filc_freeを呼び出して解放
  • 長さ0のAllocationRecordへのポインタは、共通の“空”AllocationRecordに書き換え
  • free忘れによるメモリリークをGCが補償
  • ローカル変数のアドレスがスコープ外で使われる場合、ヒープに昇格しGC管理

標準関数の特殊対応(memmove例)

  • memmoveなど任意メモリ操作関数は、invisible_bytesも正しくコピー
  • バイト単位のmemmoveと、アライン済みポインタ単位のmemmoveで挙動が異なる

Fil-Cの追加的な複雑性

  • スレッド対応:GCや解放のタイミングが複雑化
  • アトミック操作:ポインタとAllocationRecord*の同時操作が必要
  • 関数ポインタ:AllocationRecordに関数ポインタ情報を保持し、型安全性を確保
  • メモリ最適化:invisible_bytesの遅延確保やAllocationRecordとvisible_bytesの統合
  • パフォーマンス最適化:安全性の代償としての性能低下への対策

Fil-Cの利用シーン

  • 既存C/C++コードのメモリ安全性確保

    • GC導入と性能低下を許容できる場合の一時的措置
  • ASanのようなバグ検出目的での利用

  • コンパイル時評価の安全性確保(例: Zig)

  • ポインタプロヴナンス(ポインタの来歴管理)の具体例

    • 例: if (p1 == p2) { f(p1); }if (p1 == p2) { f(p2); }の違いが明確化
    • AllocationRecord*の伝播による違いが生じるため、単純な書き換えが無効

まとめ

  • Fil-CはC/C++に本格的なメモリ安全性をもたらす
  • GC導入やパフォーマンス低下などのトレードオフ
  • 既存資産の安全化やバグ検出の補助、型安全な関数ポインタ運用などに有用

Hackerたちの意見

Fil-Cは、今まで見た中で一番過小評価されてるプロジェクトだと思う。「安全のためにRustで書き直せ」って言うのは、Cプログラムを完全にメモリ安全にコンパイルできるのに、ちょっとバカみたいに聞こえる。
ここではなくて、たくさんの議論があるよ: Fil-Qt: Fil-C体験のためのQtベースのビルド (143ポイント, 3ヶ月前, 134コメント) https://news.ycombinator.com/item?id=46646080 LinuxサンドボックスとFil-C (343ポイント, 4ヶ月前, 156コメント) https://news.ycombinator.com/item?id=46259064 freetype、fontconfig、harfbuzz、graphiteをFil-Cに移植した (67ポイント, 5ヶ月前, 56コメント) https://news.ycombinator.com/item?id=46090009 Fil-Cについてのメモ (241ポイント, 5ヶ月前, 210コメント) https://news.ycombinator.com/item?id=45842494 Fil-Cを使ったdjbのメモ (365ポイント, 6ヶ月前, 246コメント) https://news.ycombinator.com/item?id=45788040 Fil-C: メモリ安全なC実装 (283ポイント, 6ヶ月前, 135コメント) https://news.ycombinator.com/item?id=45735877 Filの信じられないガーベジコレクタ (603ポイント, 7ヶ月前, 281コメント) https://news.ycombinator.com/item?id=45133938
ありがとう、嬉しいよ! > 「安全のためにRustで書き直せ」って言うのは、確かにバカみたいだね。公平に言うと、Fil-CはRustよりかなり遅いし、メモリも多く使う。でも、Fil-Cは安全な動的リンクをサポートしてて、Rustよりも厳密に安全だよ。トレードオフだから、自分が感じることをやればいいんじゃないかな。
Fil-Cには二つの大きな欠点がある。プログラムが遅くなることと、非Fil-Cコード、特にlibcとも互換性がないこと。それがあるから、Linux以外のシステム(BSDやmacOSでも)で使うのが難しくなるし、他の安全な言語との統合も複雑になる。
新しいソフトウェアはRustで書く方が理にかなってると思う。既存のC/C++ソフトウェアを同じコードベースでRustに完全に書き直すよりもね。Fil-Cは、既存のCやC++ソフトウェアでそのまま使えるから、高価でバグだらけの書き直しをせずに済むし、これらの言語でよく見られるメモリ破損バグに対する迅速な保護層として機能する。
> Fil-Cは、今まで見た中で一番過小評価されてるプロジェクトだと思う。C/C++プログラマーに「ガーベジコレクタをプログラムに追加できるよ」って言った時、彼らの目が輝いたのを最後に見たのはいつ?
Fil-Cはかなり遅いよ、タダのランチはないからね。言語を速くてメモリ安全にしたいなら、コードの適切な静的解析を可能にするための制約を追加する必要がある。
Fil-Cの問題は、ランタイムのメモリ安全性だね。メモリ安全でないコードを書くことはまだできるけど、今はクラッシュすることが保証されてるだけで、潜在的な脆弱性にはならない。コンパイル時にメモリ安全が保証される方が、機能的に正しくてメモリ安全なプログラムを気にするなら、明らかに良いアプローチだよ。もし、ウェブAPIのように信頼できないユーザー入力を受け取るものを書いてるなら、メモリ安全の問題は結局、サービス拒否の脆弱性につながる。これは良いけど、まだ完璧ではないね。Fil-Cの作業を貶めるつもりはないけど、ランタイムアプローチには限界がある。
いくつかのポイントがあるけど、他の人も触れてるね。1. Fil-Cは遅くて大きい。かなり目立つよ。もし遅くて大きいのが気にならないなら、ここ10年で考えるべきリライトはRustじゃなくて、もっと早くにJavaかC#だったはず。それがFil-Cの存在を否定するわけじゃないけど、指摘しておきたい。2. まだCを書いてるよね。プログラムが完成してるか、たまにメンテナンスするだけならそれでいいけど。僕もキャリアの大半をCでやってきたけど、別に悲惨な言語じゃないし、リライトを避けられるのはいいこと。でも、新しいコードを書くならRustの方がずっといいよ。Rustを学んでからはCを書くのをやめた。3. これはランタイムの安全性で、もっと必要かもしれない。Rustはもう少し安全性を提供してくれることが多いし、コンパイル時にFil-Cがランタイムでしかチェックしないことを表現できることもあるけど、全てが必要な場合もある。WUFFSみたいな言語はそれを提供してくれる。WUFFSにはランタイムチェックがない。コンパイル時にコードが安全であることを証明しているから、ランタイムで絶対的に安全に実行できる。あなたのコードが間違っているかもしれない。もしかしたらWUFFSのGIFフリッパーが実際にはカエルのGIFを紫にしてしまうかもしれない。でも、クラッシュしたり、GIFに隠されたx86マシンコードを実行したりすることはない。それが全てのポイントなんだ。
僕は毎日仕事でC++を書いてるけど、Fil-CがRustと同じことをしてるって主張するのは頭が悪いと思う。Rustでリライトする人がバカだって言ってるみたいだし。Fil-Cが大好きだよ。過小評価されてる。RustやAdaとは同じニッチじゃない。
Fil-Cは、既存のCプログラムを再コンパイルして、結果のパフォーマンスをあまり気にせずに追加の安全性を得たい場合にのみ良い。そんな場合でも、既存のコードはすでに十分テストされていて(ほぼ)バグがないはずだから、役に立つか疑わしい。新しいコードを書く必要があるなら、Fil-Cを使う理由はないよ。もっと良い言語があって、組み込みのセキュリティメカニズムがあるんだから。
これは「ファットポインタ」技術の別のバリエーションで、セキュリティ保証が不十分だったり、非ファットABIの境界を越えられなかったり、オーバーヘッドが発生するために、何度も実装されては却下されてきたものです。
ファットポインタをネイティブにサポートする新しいハードウェアの波が来てるから、ちょっと早すぎるかもしれないよ。それに、filcは単なるファットポインタじゃないからね。
ハードウェアメモリタグ付けは、いくつかのプラットフォームで利用可能になってるよ。
https://github.com/hsaliak/filc-bazel-template で、これら二つを使ってヘルメティックビルドを作りたい人のためにBazelターゲットを作ったよ。
この文の意味がわかる人いる? > 「到達不能なAllocationRecordを解放する際は、filc_freeを呼び出してください。」意図は、到達不能なARを解放する前に、そのvisible_bytesとinvisible_bytesフィールドが指すメモリを解放するってことだと思う。
chibicc/slimccみたいなものにinvisicapsを追加するのは面白い練習になるかも。参照カウントや、少しの間接参照の代償でメモリ節約を提供するような、見えない能力システムのバリエーションを試す余地がある。
Fil-Cはデータ競合の下ではメモリセーフじゃないんだ。能力やポインタの値が代入の際に壊れちゃうから、もしスレッドの実行順序が間違うと、間違ったポインタを通じてオブジェクトにアクセスできちゃって、プログラムが変な動作をすることがある。これに関してFil-Cの支持者(著者も含めて)が指摘する人を黙らせようとするのは、ちょっと問題だと思う。