Waylandで日本語入力への道: dynamic_castがならなくて編

adventar.org

Kernel/VM Advent Calendarの何日目かの記事です. Waylandで日本語入力をしようAdvent Calendarじゃないですよ.

前回のあらすじ: vtableをごりごり作ってコンストラクタを呼んであげた. これで動くんじゃない? そんなわけないんですけどね.

DISCLAIMER: waylandで日本語入力したい時にこの記事は役に立たないし, RustでC++のbindingをしたい時にもやめた方がいいと思う.

vtableを埋めたし動くかな?

さて前回コンストラクタをちゃんと呼んだことで, fcitx5が自作のアドオンを読んで動くようにはなりました. これで設定画面にも"T-Code"(自作のIMEの名前, いまさら?)がでてきて, IMEの選択ができるようになります. じゃあ, ということでT-Codeに切り替えてみましょう.

I2023-12-10 18:18:39.807909 addonmanager.cpp:193] Loaded addon tcode

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7ab1c7b in __dynamic_cast () from /usr/lib/gcc/x86_64-pc-linux-gnu/13/libstdc++.so.6
(gdb) bt
#0  0x00007ffff7ab1c7b in __dynamic_cast () at /usr/lib/gcc/x86_64-pc-linux-gnu/13/libstdc++.so.6
#1  0x00007ffff7f7bcd4 in fcitx::InputMethodEngine::subModeLabel[abi:cxx11](fcitx::InputMethodEntry const&, fcitx::InputContext&)
    (this=this@entry=0x5555556f1660, entry=..., ic=...)
    at /dev/shm/portage/app-i18n/fcitx-5.1.5/work/fcitx5-5.1.5/src/lib/fcitx/inputmethodengine.cpp:24
...
(gdb) up
#1  0x00007ffff7f7bcd4 in fcitx::InputMethodEngine::subModeLabel[abi:cxx11](fcitx::InputMethodEntry const&, fcitx::InputContext&) (
    this=this@entry=0x5555556f1660, entry=..., ic=...)
    at /dev/shm/portage/app-i18n/fcitx-5.1.5/work/fcitx5-5.1.5/src/lib/fcitx/inputmethodengine.cpp:24
24          if (auto *this2 = dynamic_cast<InputMethodEngineV2 *>(this)) {
(gdb) list
19          return overrideIcon(entry);
20      }
21
22      std::string InputMethodEngine::subModeLabel(const InputMethodEntry &entry,
23                                                  InputContext &ic) {
24          if (auto *this2 = dynamic_cast<InputMethodEngineV2 *>(this)) {
25              return this2->subModeLabelImpl(entry, ic);
26          }
27          return {};
28      }

はい, クラッシュしました. 場所はここで, dynamic_cast しているところですね.

fcitx5/src/lib/fcitx/inputmethodengine.cpp at c297bb7900e800e6d08cd59464c62dd6fc8cdd61 · fcitx/fcitx5 · GitHub

dynamic_cast では対象の変数の型情報を読んで, castできるかを判定します. その型情報へのポインタはvtableの関数が並ぶ1つ前のエントリにあります. ということで, 前回さぼって関数だけ並べたのが無事にクラッシュふませたということです.

型情報を入れよう

ということで型情報を入れて, vtableを完全体にしましょう. まず fcitx5 本体の InputMethodEngineの型情報をリンクしてきます.

extern "C" {
    #[link_name = "\u{1}_ZTIN5fcitx17InputMethodEngineE"]
    static TCodeEngine_Type_Info: *const std::os::raw::c_void;
}

型情報の型はよくわからない(し, 特に知る必要もない)ので, とりあえずvoid*で受けておきます.

完全なvtableの定義はこんな感じで.

struct EngineVTableFull {
    offset: u64,
    type_info: *const *const std::os::raw::c_void,
    vtable: EngineVTable,
}

これらを使って完全なvtableとして vtable_full をtcode_factory_create()の中で作っていきます. 本当は TCODE_ENGINE_VTABLE のように, 完全なvtableも constで作っておきたいところですが, リンクして持ってくる TCodeEngine_Type_Info がstaticでconstから参照できないので, ヒープに確保しておきます.

    let engine = Box::new(TCodeEngine {
        vtable: std::ptr::null_mut(),
        d_ptr: 0,
    });
    let ptr = Box::into_raw(engine) as *mut _;
    TCodeEngine_ctor(ptr);
    let mut engine = Box::from_raw(ptr);

    let mut vtable_full = Box::new(EngineVTableFull {
        offset: 0,
        type_info: &TCodeEngine_Type_Info,
        vtable: TCODE_ENGINE_VTABLE,
    });
    engine.vtable = &mut vtable_full.vtable;
    Box::leak(vtable_full);
    Box::into_raw(engine) as *mut _

vtable・engineが関数の終わりで消えないように, Box::leak・Box::into_raw しておきましょう. 厳密にはメモリリークしていきますが, InputMethodのインスタンスはこのaddonに1つなので, まあいいでしょう.

ということで動かすと…

thread '<unnamed>' panicked at 'not yet implemented: override_icon', fcitx5-tcode/src/lib.rs:275:5   
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace        
terminate called without an active exception

と…とりあえず, dynamic_cast はできるようになったっぽいです… じゃあ, この override_icon をとりあえず簡単に実装したいわけですが…

fcitx5/src/lib/fcitx/inputmethodengine.h at c297bb7900e800e6d08cd59464c62dd6fc8cdd61 · fcitx/fcitx5 · GitHub

virtual std::string overrideIcon(const InputMethodEntry &) { return {}; }

オオ, std::string を返すのか… rustから? どうやって?? ということで

次回: Waylandで日本語入力への道: std::string の捏造編

gentoo.hatenablog.com