Exevalator 2.0 をリリース、互換性に注意が必要なバグ修正が 1 件

RINEARNでは先日、オープンソースの式計算ライブラリ「Exevalator(エグゼバレータ)」の最新版 Ver.2.0.0 をリリースいたしました。
2.0 のリリースと言っても、今回のアップデートは、1件のバグ修正のみを含むもので、ソースコード上の改修規模はごくわずか(数行)です。
ただ、その修正対象のバグが、バグなのか仕様なのか分かり辛かったものであり、恐ら仕様と思われて使われていたケースも十分考えられるものでした。 そのため、今回は単なるバグ修正ではなく、「互換性を壊す仕様変更」の側面も帯びている事を踏まえて、念のためメジャーバージョン番号を 1 から 2 へ上げた、という状況です。
本記事では、その概要を解説いたします。
そもそも Exevalator とは?
−ユーザーが入力した計算式などの値を算出するライブラリ
まず最初に、Exevalator(エグゼバレータ)について簡単に解説いたします。
Exevalator は、アプリやソフトウェアに部品として組み込んで使う「ライブラリ」の一つで、ユーザーが入力した計算式の値を算出したりするのに使えます。
例えば何らかのアプリを開発していて、ユーザーがそのアプリ上で
や
のような計算式を自由に入力し、その値を求めたい、という場面はしばしば生じます。 ただ、こういった処理は、一見単純に思えるのですが、実は作るのが結構ややこしくて難しく、なかなか大変です。
そこで Exevalator は、こういった処理をライブラリ(部品プログラム)として提供する事で、手軽に実現できるようにしたものです。 Java/C++/C#/Rust の4つの言語に対応しており、1〜2枚のソースコードを、プロジェクトのソースコードフォルダに放り込むだけですぐに使えます。
Exevalator についてのより詳しい情報は、以下の公式サイトをご参照ください:
Ver.1 では、組み込み関数を自作した際、引数の渡され方にバグが存在
ここからは、本題のバグの内容について解説いたします。
組み込み関数の実装と動作の流れ
Exevalator は、アプリのユーザー側が関数などを自作する事はできないのですが、アプリの開発者側が関数を用意して、Exevalator に組み込む事ができます(以下、組み込み関数)。 組み込まれた関数は、ユーザーが計算式の中で使えるようになります。
例えば、Exevalator に関数 fun(a, b, c) を組み込むと、ユーザーは計算式の中で、以下のように関数 fun を呼び出して使う事ができるようになります:
この関数 fun は、アプリの開発言語(Java/C++/C#/Rust のどれか)で実装し、Exevalarotに登録して組み込みます。 例えば Java 言語の場合は、関数は以下のように実装します:
上のクラス MyFunction (名前はなんでも可)をインスタンス化して Exevalator に登録します。その際に、式内で呼び出す際の関数名 "fun" を指定します。
すると、先ほどのように式内で関数呼び出し fun(123, 456, 789) を行った際に、上記の MyFunction の invoke メソッドが呼び出され、同メソッドの配列引数 args に、式内での関数呼び出しの実引数 123, 456, 789 が渡されます(詳細は次節)。 その引数の値に基づいて、invoke メソッド内で計算などを行い、結果を戻り値として返すと、それが式内での fun(123, 456, 789) の値として使用されます。
引数が渡される際、配列 args への格納順序が本来とは逆に
今回問題となったのは、上記のような計算式内での関数呼び出しの実引数「 123, 456, 789 」が、invoke メソッドの配列引数 args に格納される際の、要素の順序です。
直観的には、以下のような順序で格納される事を誰もが期待するはずです:
args[1] = 456
args[2] = 789
実際、今回のリリース(Ver.2.0)以降では、引数は上記の順序で格納されて渡されます。 そのように挙動を修正した、というのが今回のアップデートです。
では、以前の Ver.1.0.x ではどうなっていたかというと、以下のように、逆順で格納されていました:
args[1] = 456
args[2] = 123
これは、何か意図があってのものではなく、式の構文解析を行うパーサの実装の不備※によるものでした。
※ 各引数(一般に部分式になり得る)の構文木ノードは、左から順に解析された上でスタックに積まれますが、それを単純に取り出すと、詰んだ順とは逆の順序(LIFO順)になります。 従って、取り出し後に順序反転をして、積む際の順序に戻す(FIFO順にする)必要があるのですが、そのままになっていました。
Exevalator の構文解析処理(パーサ)は、Java製スクリプトエンジン Vnano のものをシンプル化しつつ他言語移植したものですが、 その移植の際に、順序反転の行が抜け落ちてしまったのが原因でした。
バグではなく仕様と判断されているケースも存在し得るため、併せて「非互換な仕様変更」を含み得るメジャーバージョンアップ扱いに
しかしながら args 配列の要素の順序に関しては、特にドキュメント内での記載が無く(バグが無ければごく自然な順序になっているはずであったため)、 そのため この挙動がバグなのか仕様なのかが分かり辛い状況だった かもしれません。 実際、今回のバグが見つかった経緯は、使用を試されている方から、バグか仕様かのお問合せをいただいた事によるものでした。
従って、このバグによる順序を「そういう仕様」と思って使用されているケースも、恐らく一定数存在するはずという前提で対応する必要があります。 そのようなケースでは、この順序を正しい形に戻す事は、互換性を壊す仕様変更とほぼ同じ側面を持っています。
ふつう、バージョンを表す Ver.1.0.x のようなコードにおいて、「x」のような末尾の番号は、上がっても互換性が大きく壊れないような小さなアップデートの番号を表す事が多いです(少なくとも RINEARN 製のソフトではそうしています)。 従って、今回のバグ修正において、Ver.1.0.0 → Ver.1.0.1 のように末尾の番号を上げるだけだと、上記の「仕様と判断して使われている」というケースでかなりの混乱を招いてしまいます。
そのため、今回のバグ修正はわずか数行のものでしたが、安全のため先頭の数字を上げて、Ver.2.0.0 としてリリースした次第です。
なぜこのバグが自動テストを通過していたのか?
Exevalator のソースコードリポジトリ 内には、テストコードも同梱されており、push 時に自動でテストが走るようになっています。 その中で、関数については以下のような呼び出しパターンがテストされていました:
funB(2.5)
funC(1.25, 2.5)
funC(funA(), funB(2.5))
funC(funC(funA(), funB(2.5)), funB(1.0))
funC(1.0, 3.5 * funB(2.5) / 2.0)
funA() * funC(funC(funA(), 3.5 * funB(2.5) / 2.0), funB(1.0))
2 + 256 * funA() * funC(funC(funA(), 3.5 * funB(2.5) / 2.0), funB(1.0)) * 128
上記の通り、結構ねじった呼び出しパターンのテストが存在しますが、今回のバグは、これらを全て通過(合格)してしまっていました。
一見すると、引数の評価順序が逆になっていたら、上記のテストは絶対に通らないような気がします。 しかしなぜ通ってしまっていたかを調べると、「テストで用いていた関数 funC が、引数の和を返すものになっていた」というのが原因でした。 複数の値の和は、値の順序を入れ替えても同じ結果になるためです。
そのため、今回はテストコードに、「複数の引数の順序と値が、共に正しい事をテストする関数 funD 」を用いたものを追加しました。
なぜ今まで公式側で見つからなかったのか?
このバグで非常に恥ずかしい点は、お問合せいただくまで公式側で見逃してしまっていた事です。 自動テストを通過していても、実際に組み込み関数を実装して使えば、すぐに分かったはずです。
これについては、「公式側での Exevalator の実用頻度が少なすぎた」という所に根本原因があります。
そもそも Exevalator は、先行してJava言語で開発したスクリプトエンジン「 Vnano 」の処理系が当初想定よりも大きくなってしまったため、 機能量を絞ってコンパクト化しつつ、複数言語(Java/C++/C#/Rust)に移植したものでした(詳細はこちら)。
しかし、RINEARNで何か開発する際はJava言語を採用する事が多く、その場合の式計算などでは、やはり機能量が多い Vnano の方を使用してきました(次期版リニアングラフ3Dでもそうです)。 そのため、Exevalator は開発してリリースしたものの、公式側としては実用場面がほぼ無いという状態が続いていました。 これが、今回のような大きなバグが長期で残り続けた一番の原因だと考えています。
これについての根本的な対策は、やはり どんなライブラリでも、公式側として最低何か 1 つでも実用アプリケーションを作って使い倒す という事が重要なのだと思いました。
従って、RINEARN でも今後、何かしらの Exevalator を用いたアプリかコマンドラインツールなどを製作してリリースしたいと考えています。 まだ内容は未定ですが、一つ考えているのは、「 Windows / Linux / macOS で使える、RustかC++製の式計算コマンド 」などです。
お詫びと謝辞
今回のバグ修正についての詳細は、以上の通りです。
今回のバグにより、混乱をお掛けしてしまった方々には、深くお詫び申し上げます。 引数順序が逆になっているとは、本当に初歩的なパーサ実装のミスなのですが、まさか上記のテストを全て通過しているのにそんな事が起こっているとは全く思わず、本当に申し訳ありません…
また、今回のバグについてお問合せをいただいたユーザー様には、この場をお借りして心より感謝申し上げます。
今後も、Exevalator に関する情報は、このお知らせコーナーで逐次お知らせしていきます。

リニアングラフやVCSSLの最新版をリリース、目盛りの位置や内容を自由に指定可能に!
2024-11-24 -
リニアングラフ3D/2Dを更新し、自由な位置に、自由な表記内容の目盛りを描けるようになりました! 併せて、Java言語やVCSSLでの、プログラム制御用APIも拡張しています。詳細をお知らせします。

Exevalator 2.2 をリリース、TypeScript 対応によりWebブラウザ上で動作可能に
2024-10-22 -
オープンソースの式計算ライブラリ「Exevalator(エグゼバレータ)」の2.1をリリースしました。新たに TypeScript に対応し、Webブラウザ上での式計算にも使えるようになりました。詳細を解説します。

アシスタントAI作成の舞台裏(その2、作成編)
2024-10-12 -
アシスタントAIの作成方法解説の後編です。実際にChatGPTの「GPTs」機能を用いて、アシスタントAIを作成する手順や、独自の知識をもたせたり、精度を出すためのノウハウなどを解説しています。

アシスタントAI作成の舞台裏(その1、基礎知識編)
2024-10-07 -
アシスタントAI作成方法解説の前編です。今回はまず、アシスタントAIを作る前に抑えておきたい、基礎知識を延々と解説しています。そもそもLLM型AIとはどんな存在か? RAGとは何か? 等々です。

ソフトの利用をサポートしてくれるアシスタントAIを提供開始!
2024-09-20 -
RINEARN製ソフトの使い方の質問応答や、一部作業のお手伝いをしてくれる、アシスタントAIを提供開始しました。ChatGPTアカウントさえあれば、誰でも無料で使用できます。使い方を解説します。

Exevalator 2.1 をリリース、新たに Visual Basic に対応
2024-07-28 -
オープンソースの式計算ライブラリ「Exevalator(エグゼバレータ)」の2.1をリリースしました。今回から、新たに Visual Basic(VB.NET)でも使用できるようになりました。詳細を解説します。

関数電卓 RINPn(りんぷん)、Esc キーで計算式の一発クリアが可能に
2024-07-20 -
関数電 RINPn の Ver.1.0.2 をリリースしました。今回から、キーボードの「 Esc 」キーを押すと、入力中の計算式を一発でクリアできるようになりました。詳細を解説します。

Exevalator 2.0 をリリース、互換性に注意が必要なバグ修正が 1 件
2024-07-14 -
オープンソースの式計算ライブラリ「Exevalator (エグゼバレータ)」の2.0をリリースしました。今回の更新では、互換性に注意を要する 1 件のバグ修正があります。詳細を解説します。

各ソフトウェアをアップデート、リニアングラフのコマンド拡張やVCSSLの英語対応など
2024-02-05 -
各ソフトの一斉アップデートの内容をお知らせします。今回は、リニアングラフのコマンド機能を大幅拡張したのがメインです。また、VCSSLのメッセージ類の英語対応も行いました。

Vnano の Ver.1.1 で実装した反復計算高速化の内側
2024-01-17 -
前回のお知らせ記事の続編です。スクリプトエンジン Vnano の Ver.1.1 において実施した高速化を、エンジン内部の実装面から掘り下げて解説します。