0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Lua/Solにおけるコード注入テクニック

Posted at

コード注入

ここで言うコード注入とは、スクリプトファイルの冒頭などに処理系自身がコードを挿入し、ちょっとした機能を追加すること。

例えば.luaファイルそれぞれの冒頭に local __MODULE__ = {ファイルパス} というコードを挿入しておくと、__MODULE__という変数名で自身のファイルパスを取得できるようになる。
ファイル作成者が毎回ボイラープレートを書かなくて済むので便利。

応用例は以下のように、require関数を再定義して相対パスによるモジュール読み込みを可能にするなど。

// ストレージからluaスクリプトを取得し、冒頭に「自身のファイルパス」を保持するコードを付加する。
std::string get_code_injected(const std::filesystem::path &file_path) {
    std::string code = /* ストレージからluaファイルを読み込む */
    std::string cwd = file_path.parent_path().string();

    std::string header =
        "local __CWD__ = '" + cwd + "';" +
        "local __original_require = require;"
        "local function require(module)"
        "    __CWD_global__ = __CWD__"
        "    return __original_require(module)"
        "end;";

    return header + code;
}

// package.searchersにも独自関数を追加し、その中で`__CWD_global__`を参照する。
// `__CWD_global__`からの相対パスでファイルを検索すればよい。

工夫

ここからはSolライブラリ(C++内でLuaの実行環境を簡単に作れる)を使用時の、具体的な注意点をまとめる。
(sol2 v3.3.0)

挿入コードはminifyして1行に圧縮する

Luaは改行文字を特別扱いすることはない。
あらゆるLuaのコードは改行を含まないように書き直すことができる。

local a = 9
print(a)

このコードは、実は以下のように書き直せる。

local a = 9 print(a)
-- または
local a = 9;print(a)

トークンが適切に区切れてさえいればいいらしい。セミコロンも使えるが、必須ではないのがC言語と違って面白いところだ。

さて、この事実を使うことで、挿入するコードを1行に圧縮することができる。

Solはスクリプトの実行中にエラーに当たると、エラーの発生行番号を表示してくれるのだが、コードを複数行挿入しているとその行番号が元々のファイル内容とズレてしまう。
このズレを防止するのが、挿入コードを1行に圧縮する意義である。

chunkname引数の活用

sol::state::script()関数やsol::state::load()関数は、Luaコードを文字列として与えることでそれを実行してくれる。
しかしこのままだと、このコード自体には「ファイル名」などという概念が存在しないので、エラーが発生したときも何だかよく分からない表示が出てくる。
これに対処するために、第2引数chunknameとして追加の「実質的なファイルパス」等の情報を教えておくことができる。これでエラー発生時に“ファイルパス”を表示してくれるようになる。

lua.script(code, "@scripts/hoge.lua")

この時さらに注意点として、ファイルパスには先頭に「@」を付けておかないと、またもや少しおかしな表示となってしまう。

何もしない chunkname指定(@なし) chunkname指定(@あり)
image.png image.png image.png
ファイル名は存在しないので、コード内容がそのまま表示されている。 [string "パス"]のような表示になってしまう。 ファイルパスと行番号が綺麗に表示される。

まとめ

  • Lua実行環境自身がスクリプト冒頭などにコードを挿入しておくことで、様々な機能追加が可能
  • 挿入コードはminifyして1行にしておくことで、エラー行通知と実際のファイル行の齟齬を無くせる
  • Solのchunkname引数にファイルパスを与えるときは、先頭に「@」を追加すべし

コード注入自体、些かハッキーなテクニックであるかもしれないが、こうした書き方にも耐えうる拡張性を持っていることがLua言語の強みでもある。

この記事を書くきっかけの話。当初はエラー行がズレてしまう現象に悩まされていたのだが、挿入コードを無理矢理1行に圧縮できることに気付いて目が覚めたような気持ちになった。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?