Qt 5 の Qt Quick アプリのバイナリには qml のソースコードが含まれている

はじめに

みなさん Qt Quick でアプリ書いていますか?便利でいいですよね!

最近こんな相談を受けました。

Qt Quick でアプリを書いているのですが、アプリのバイナリの中身に qml や js のソースコードがそのまま含まれているのですがなんとかなりませんか?

状況と原因を調査

以下の Qt Quick の簡単なプロジェクトで状況を確認してみましょう。

app.pro

QT = quick
SOURCES = main.cpp
RESOURCES = qml.qrc

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

qml.qrc

<RCC>
    <qresource prefix="/">
        <file>main.qml</file>
    </qresource>
</RCC>

main.qml

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
}

これをコンパイルして中身を見てみましょう。

バイナリエディタで確認

ソースコードが丸見えの状態

Qt に詳しい方は分かるかと思いますが、このプロジェクトでは qml のソースコードを Qt のリソースシステム に格納し、それをテキストファイルとして読み込んで実行しています。

いい対策は無いか

上記のリソースのドキュメントの Compression に記載があるとおり、リソースシステムには圧縮機能があり、これが機能すれば、少なくとも生のファイルが見えることはなくなります。

実際の rcc の実行コマンドは以下の通りになっているので(Qt Creator のビルドのログなどを参考にしてください)

$ /path/to/rcc -name qml ../app/qml.qrc -o qrc_qml.cpp

以下のように内部情報の表示を増やしてみましょう。

$ /path/to/rcc -name qml ../app/qml.qrc -o qrc_qml.cpp –verbose
Qt resource compiler
Processing 1 files [listMode=0]
Interpreting ../app/qml.qrc
Outputting code
main.qml: note: not compressed

理由は不明ですが、”not compressed” ということで、生データがそのままリソースに組み込まれていることがわかります。

ドキュメントには、「デフォルトではしきい値は 70% にしてあるよ」と書いてあるので、これを下げて圧縮が効くか試してみましょう。

$ /path/to/rcc -threshold 25 -name qml ../app/qml.qrc -o qrc_qml.cpp –verbose  
Qt resource compiler
Processing 1 files [listMode=0]
Interpreting ../app/qml.qrc
Outputting code
main.qml: note: not compressed

$ /path/to/rcc -threshold 15 -name qml ../app/qml.qrc -o qrc_qml.cpp –verbose  
Qt resource compiler
Processing 1 files [listMode=0]
Interpreting ../app/qml.qrc
Outputting code
main.qml: note: compressed using zlib (139 -> 116)

ということで、しきい値を下げることで(ここではzlib)圧縮が効くことがわかりました。

rcc コマンドを手動で毎回実行するわけにもいかないので、app.pro に以下の行を追加しましょう。

QMAKE_RESOURCE_FLAGS += -threshold 15
ソースコードが圧縮されている状態

だいぶましになりました。

が、Qt のリソースシステムに詳しい人であれば、この内容からソースコードの復元(解凍)が可能ではあります。

QML をコンパイル時にコンパイルするのはどうか

Qt には QML をコンパイルする機能 があり、以前は商用版でのみ利用可能でしたが、Qt 5.11 以降はオープンソース版でも使えるようになっています。

app.pro を以下のように変更してみましょう。

#QMAKE_RESOURCE_FLAGS += -threshold 15
CONFIG += qtquickcompiler

コンパイルした QML がバイナリに含まれるのでソースコードは含まれない…という想定に反し、なんとソースコードが見える状態になっています。

qml のソースコードがなぜか含まれている

[QTBUG-88147] qtquickcompiler requirements have changed というバグレポートを見ると、

「Qt 5.14 までは元のファイルをアプリのバイナリに含めないようにしていたが、[QTBUG-73669] Adding *.js to resources of project without QML leads to linker error のバグを直すために Qt 5 ではソースファイルもバイナリに含めるようにしたよ」

というようなことが書いてあります。

というわけで、Qt 5 ではもうあきらめるしかないということになりますので、以下のようにして祈りましょう。

QMAKE_RESOURCE_FLAGS += -threshold 15
CONFIG += qtquickcompiler

納得がいかないなぁ

大多数のケースでは QTBUG-73669 の条件に当てはまらないと思うので、qmlcachegen という .qml や .js のコンパイルをしているツールのソースコードを局所的に元に戻すことでソースコードをバイナリに含めなくすることも可能だと思います。

ソースコードの秘匿化や、バイナリサイズの削減などでこういったことが必要であれば、調査もしますし対応も考えますので一声かけてください!