Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】The 'or' that read too far

2026年10月27日に「Dev.to」が公開したITニュース「The 'or' that read too far」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

自作言語の「or」演算子で、片方が真ならもう片方を評価しない挙動が、各実行環境(ネイティブ、インタプリタ、WebAssembly)やコンパイラで異なり、バグを引き起こした。これにより、本来実行されないはずの危険な処理が実行された。演算子の評価順序の重要性を再認識させた事例だ。

出典: The 'or' that read too far | Dev.to公開日:

ITニュース解説

プログラミング言語における「or」という言葉は、非常にシンプルに見えるが、その裏側には複雑な動作原理が隠されている場合がある。この記事では、Verboseという言語の「or」演算子が、異なる実行環境で予期せぬ挙動を示したバグとその解決策を通じて、プログラミングの深い側面を解説する。

「or」演算子には、主に二つの重要な側面がある。一つはその「値」で、これは左右どちらか一方が真であれば結果は真となるという、誰もが知る論理和の真偽値表の通りだ。もう一つは「順序」、つまり左右のどちらの式が、いつ計算されるかという評価の順番である。この評価順序には大きく分けて二つの方式がある。「すべてを先に計算する」方式では、左右の式を両方とも評価してから結果を組み合わせる。一方、「ショートサーキット評価」方式では、まず左側の式を評価し、その結果が真であれば、右側の式は評価せずに全体の「or」の結果を真と確定する。右側の式を評価することが、例えば空のテキストの最初のバイトを読もうとするような、プログラムにとってリスクを伴う危険な操作である場合、このショートサーキット評価がプログラムの安全性を保つ「ガード」として機能する。これは単なる処理時間の最適化ではなく、危険な操作からプログラムを守るための、非常に重要な評価順序の約束なのだ。

Verbose言語で書かれたプログラムは、三つの異なる方法で実行される。一つは「インタプリタ」で、これはプログラマーが書いた命令を逐次読み解き、その場で実行していく方式だ。ちょうど料理のレシピを読み上げながら、その通りに作業を進める人に似ている。次に「ネイティブバイナリ」は、プログラムが一度プロセッサが直接理解できる機械語に翻訳されたもので、パソコン上で直接実行できる。これはレシピが一度プロセッサの動きに最適化された手順書に翻訳されるようなものだ。そして「WebAssembly(WASM)モジュール」は、ブラウザやNode.jsといった環境で動くために作られた、仮想的な機械語に翻訳されたものだ。

Verbose言語の「or」が正しく機能し、ショートサーキット評価を行っていたのは、ネイティブバイナリにコンパイルされた場合だった。例えば、「テキストが空であるか、最初のバイトがゼロより大きいか」という条件式では、入力テキストが空であれば、ネイティブバイナリは左側の条件(テキストが空であるか)が真だと判断した時点で、右側の条件(最初のバイトを読む)を評価しない。アセンブリコードを見ると、左側が真だった場合に、右側の評価をスキップして結果を出すための「ジャンプ」命令が明確に存在しており、このジャンプがショートサーキット評価の本質だった。これにより、空のテキストの最初のバイトを読もうとする危険な操作は完全に回避されていた。

しかし、同じプログラムをインタプリタで実行すると、空のテキストの場合にエラーが発生した。インタプリタは「or」の左右の式をどちらも先に評価してしまう「すべてを先に計算する」方式で実装されていたためだ。テキストが空であっても、まず左側の条件が評価されて真となるが、その後すぐに右側の式である「空のテキストの最初のバイトを読む」という処理が実行され、結果として範囲外アクセスエラーが発生してしまっていた。インタプリタ内の論理和を結合する部分ではショートサーキット評価が行われていたとしても、その結合処理に至る前に、左右の式がすでに評価されてしまっていたため、安全ガードとしての役割を果たしていなかったのだ。この問題は、インタプリタが「or」演算子を処理する際に、左側が真であれば右側を評価しないように修正することで解決された。

WebAssemblyモジュールでも同様の問題が発生した。例えば、「nがゼロか、10をnで割った結果が正か」という条件式で、nがゼロの場合、ネイティブバイナリでは右側のゼロ除算を回避できたはずが、WASMではゼロ除算エラーが発生したのだ。これはWASMの「or」の実装が、二つの数値をビットごとのOR演算子(i64.or)で結合する形になっていたためだった。ビットごとのOR演算子を使うためには、左右の数値がどちらも先に計算されている必要があるため、ここでも「すべてを先に計算する」方式が採用されてしまい、結果として常にゼロ除算が発生していた。この問題も、WASMコンパイラが「or」を直接ビット演算に変換するのではなく、if文を使って左側が真であれば右側を評価しない構造に変換するように修正することで、安全なショートサーキット評価が実現された。

さらに、プログラムを実行する前に、コンパイラの内部にも「or」の評価順序に関する潜在的な問題が潜んでいた。コンパイラは、値が定数である式を、プログラムが実行される前にあらかじめ計算して結果に置き換える「定数畳み込み」という最適化を行う。これは効率的だが、問題が発生する可能性のある式にも適用されると危険だ。例えば、「1 == 1 or (-9223372036854775807 - 1) / -1 > 0」という式では、左側は常に真であり、右側の式は数学的にオーバーフローを起こす。しかし、コンパイラがショートサーキット評価を考慮せずに右側の式まで定数として計算しようとしたため、コンパイラ自体がクラッシュしてしまうバグがあったのだ。この問題は、計算が失敗する可能性のある定数式に対しては、コンパイラがその場で評価せずに、ショートサーキット評価によって実行時に評価されるべきことを尊重するよう修正することで解決された。

この一連の出来事は、「or」という単純なキーワードが持つ「片方が真ならもう片方は評価しない」という「約束」が、いかに重要であるかを教えてくれる。ネイティブバイナリだけがこの約束を守っており、インタプリタ、WebAssembly、そしてコンパイラの定数畳み込みの三つの実行パスは、この約束を忘れてしまっていた。このようなバグは、正常に動作するケースのテストだけでは発見できない。プログラムが保護しようとするエッジケース、例えば空のテキストやゼロ除算といった、危険な状況をあらゆる実行パスでテストすることが、システムの堅牢性を確保する上で不可欠なのだ。

1789文字

関連コンテンツ

関連IT用語