はじめに
前回は、1モジュールの構造を眺めたけど、今回はクレート単位で構造を取り扱う方法を考える。
またせっかく抽出するので、Rust
のAPI
を抽出し、引数や戻り値の型から逆引きする足掛かりも考える。
方針
まず、クレート全体を周回するために、どのようにしてパラメータを渡すべきか。
これは簡単で、rustc_interface::interface::Config
のinput
フィールドにクレートのルートモジュールを渡す。
クレートのルートモジュールはたいていsrc/lib.rs
になる。
例えば、標準ライブラリのstd
の収集は以下のように指定する。
ここで、sys_root
にはRust
ライブラリパスのプレフィックスが入っているものと想定している。
let sys_root = "...";
let root_crate = "std";
let file_path: PathBuf = [
&sysroot,
"lib/rustlib/src/rust/library",
root_crate, "src/lib.rs"
]
.iter().collect();
use rustc_interface::interface;
use rustc_session::config;
let rustc_config = interface::Config {
input: config::Input::File(file_path),
// 他は前回と同じ
};
上記のように指定してrustc_interface::interface::run_compiler
関数を走らせると、クレート内の全ての構成要素を操作できるようになる。
APIの抽出
API
の関数識別子、引数及び戻り値の型を収集し、json
フォーマットで出力するサンプルツールを作成した。
- クレート名をソース上にベタ書きしてるため、
std
のみ収集します。 - 出力先も決め打ちで、カレントディレクトリの
exp
となってます。
つらかったところ
型の完全名の解決
構造内で型定義を行っているノードにはrustc_hir::def_id::DefId
が振られており、この値をSrustc_middle::hir::map::Map
(rustc_middle::ty::TyCtxt::mir
関数で取得できる)のdef_path
関数に渡すことでrustc_hir::definitions::DefPath
からの値を取得することができる。
次いでDefPath::to_string_no_crate_verbose
関数を呼ぶことで、コロン区切り(::
)の完全名を取得することができる。
ただしDefPath
の取得において幾つか制限が見受けられた。
-
self
引数の場合、xxx::xxx::Trait
とされてしまい、適切に完全名が取得できない。-
DefId
を(LocalDefId
に変換した上で)TyCtxt
のhir_node_by_def_id
関数に渡して、rustc_hir::Node
を取得し、ここから型情報を経由して、self
の実際の型解決を行なった。
-
- プリミティブ型(i8とか)は
DefId
が取得できない。-
rustc_hir::def::Res
のPrimTy
ヴァリアントを経由して型名を取得できる。
-
- いくつかの型(
socketaddr_un
など)は型名を解決できなかった。-
rustc_hir::PathSegment
のident
フィールドの値を連結してよしとした。
-
コンパイルエラー表示
クレート単体でビルドするため、依存先が解決できずにコンパイルエラーが標準エラーに出力される。
出力はされるが構造の収集には全く問題がないため、ただの邪魔な存在である。
エラー表示は、rustc_errors::DiagCtxtを経由して、rustc_errors::emitter::DynEmitterで行われる。
出力を阻害する方法として、「Rust Compiler Development Guide」に記載されたサンプルのように、rustc_interface::interface::Config
のpsess_created
にDiagCtx
の初期値を渡すことで行うことができる。
やってないこと
- 再エクスポートは拾っていない
-
std::string::String
は含まれていない。本体はalloc::string::String
のため。
-
-
std
限定- 単なる手抜き
APIの逆引き
APIの逆引きのためにデータベースに投入する。
json
をそのまま使ってインポートできるため、データベースとしてduckdb
を選択した。
まず、以下の3つのテーブルを用意した。
- prototype (関数名を保持するテーブル)
- type_symbol (引数や戻り値の型を保持するテーブル)
- type_ref (関数と型情報の関連付けを行うテーブル)
- 引数か戻り値かを判定は
enum
で用意
- 引数か戻り値かを判定は
create table prototype (
id BIGINT not null primary key,
symbol varchar(64) not null,
qual_symbol varchar(256) not null
);
create table type_symbol (
id BIGINT not null primary key,
symbol varchar(64) not null,
qual_symbol varchar(256) not null
);
create type type_kind as enum ('arg', 'return');
create or replace table type_ref (
prototype_id BIGINT not null,
type_id BIGINT not null,
kind type_kind not null,
constraint type_ref_pk primary key (prototype_id, type_id, kind)
);
次いで、データを投入する。
/* 関数プロトタイプを投入 */
insert into prototype
select
row_number() over (order by qual_symbol) as id,
symbol, qual_symbol
from 'exp/prototype.json';
/* 型情報には重複して内容が含まれるため、一旦テンポラリなテーブルに入れる */
create or replace table temp_type as
select
dense_rank() over (order by child.symbol, child.qual_symbol) as id,
t.kind, t.parent.qual_symbol as qual_psymbol, unnest(child)
from 'exp/type.json' t
join prototype p on t.parent.qual_symbol = p.qual_symbol;
/* 重複を取り除いて型情報を投入する */
insert into type_symbol
select distinct id, symbol, qual_symbol from temp_type;
/* 関数プロトタイプと型情報を関連づける */
insert into type_ref by position
select distinct p.id as pid, t.id as tid, lower(kind)
from temp_type t
join prototype p on t.qual_psymbol = p.qual_symbol
;
json
そのまま指定するだけなのでduckdb
便利。
ネストしていても、unnest
関数でカジュアルに崩せるのでduckdb
便利。
例えば、以下のようなクエリを投げれば
/* 戻り値がStringな関数を列挙する */
select t.* from prototype t
where exists (
select null from type_symbol ts1
join type_ref ts2 on ts1.id = ts2.type_id
where
ts2.prototype_id = t.id
and ts2.kind = 'return'
and ts1.symbol = 'String'
);
┌───────┬───────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ id │ symbol │ qual_symbol │
│ int64 │ varchar │ varchar │
├───────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ 2539 │ into_string_lossy │ std::sys_common::wtf8::Wtf8Buf::into_string_lossy(std::sys_common::wtf8::Wtf8Buf) -> alloc::string::String │
│ 2065 │ fill │ std::panicking::FormatStringPayload::fill(std::panicking::FormatStringPayload) -> alloc::string::String │
└───────┴───────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
な結果が得られる。
まとめ
DuckDBつよい (Rustどこいった・・・)。
4日目へ続く