【テクニカル・上級編】ON SUBSCRIPTRANGEによる配列添字範囲外アクセスの検出 – PL/Iの基本構文とデータ制御実践ガイド

制御なきバッファオーバーランの恐怖:ON SUBSCRIPTRANGEが救う基幹システムの「見えない崩壊」

夜間バッチがうなりを上げるIBMメインフレームの心臓部。数百万件の顧客データを処理する深夜の基幹ジョブで、突如として発生する原因不明のデータ破壊や、再現性のないS0C4アベンド(System Completion Code 0C4)。
メインフレームの現場を渡り歩いてきたアーキテクトなら、この悪夢のような響きに身震いした経験が一度はあるはずだ。

C言語やC++であれば、ポインタ演算の野放図さは「プログラマの自己責任」として片付けられるかもしれない。しかし、高信頼性が絶対条件であるPL/Iの世界では、この「配列の境界チェック」をどう扱うかが、システム全体の生死を分ける。今回は、PL/Iが誇る強力な動的例外処理機構、`ON SUBSCRIPTRANGE`にスポットを当て、基幹システムの信頼性を極限まで高めるための実践知を語り尽くす。

1. 予約語を持たないPL/Iの優美さと、その「諸刃の剣」

PL/Iの言語仕様における最大の特徴であり、C#やJava出身のモダンなエンジニアが最も度肝を抜かれる点。それは、「PL/Iには厳格な予約語(Reserved Words)が存在しない」という事実だ。

`IF`や`THEN`、さらには`READ`や`WRITE`といったキーワードですら、コンテキストによってはただの変数名として定義できてしまう。この柔軟性は、初期の汎用機プログラマにとって究極の自由をもたらした反面、メンテナンスフェーズにおいては「魔改造されたレガシーコード」の温床となった。

識別子の命名規則において、例えば `SUBSCRIPTRANGE` という言葉自体も、コンテキストによってはプログラマが勝手に定義した構造体名や変数名になり得る。この自由度の高さゆえに、配列の添字(Subscript)が定義された上限・下限を超えてメモリ上の隣接領域を破壊したとしても、デフォルトのコンパイラ挙動ではコンパイルエラーにもならず、そのまま野放しで実行されてしまうのだ。

ここで破壊されるのは、単なるローカル変数ではない。DB2の埋め込みSQL(SQLCA)の領域であったり、CICSのCOMMAREA(通信領域)の端であったり、あるいはベース変数(Based Variable)が指し示す重要なポインタチェーンそのものであるケースが後を絶たない。

2. コンパイラオプションと `SUBSCRIPTRANGE` の真価

この見えないメモリ破壊を防ぐための防壁が、コンパイラオプションの `STGOWNG`(Storage Overwrite Guard)`CHECK` / `NOSUBSCRIPTRANGE` である。

標準的な商用ビルド(本番環境)では、オーバーヘッドを極限まで削ぎ落とすために `NOSTG` や `NOSUBSCRIPTRANGE` が指定されていることが多い。「1ナノ秒でも速く回す」というメインフレームの宿命ゆえの選択だが、これがデバッグ時には最悪の罠となる。

配列の範囲外アクセスを検知するには、コンパイル時に以下のオプションを明示的に付与する必要がある。

1
/ コンパイラオプションのイメージ(JCLのPARM指定等) /
//COMPILE EXEC PGM=IBMZPLI,PARM=’SUBSCRIPTRANGE,STG(TRACE)’

このオプションを指定することで、コンパイラは配列参照のたびに「現在の添字が `LOWER` から `UPPER` の範囲内にあるか」の境界チェックコードをインライン展開する。当然、実行時パフォーマンスには数%から十数%のオーバーヘッドが生じる。
だが、「原因不明のメモリ破壊に何日も費やすコスト」「本番直前での境界チェックのオーバーヘッド」を天秤にかけたとき、どちらがシステムアーキテクトとして取るべき選択であるかは火を見るより明らかだ。

3. 実践:`ON SUBSCRIPTRANGE` による動的トラップとログ設計

ただコンパイルオプションを有効にするだけでは不十分だ。単にエラーでアベンドさせるのではなく、障害発生瞬間のレジスタ、インデックスの値、そして問題の配列のベースアドレスを正確に捉え、ログに吐き出させてこそプロフェッショナルといえる。

以下に、実業務のバッチプログラムで組み込むべき `ON` ユニット(例外処理ブロック)の模範実装を示す。

1
——————————————————————

  • 配列添字範囲外エラー捕捉サンプルプログラム

——————————————————————
SUBS_TEST: PROC OPTIONS(MAIN);

DCL WS-TBL(10) FIXED BIN(31) INIT((10)0); / 10要素のテスト配列 /
DCL IDX FIXED BIN(31); / 添字変数 /
DCL ERR-FLG BIT(1) INIT(‘0’B); / エラー制御フラグ /

————————————————————–

  • SUBSCRIPTRANGE 条件の有効化とONユニットの定義

————————————————————–
ON SUBSCRIPTRANGE
BEGIN;
PUT SKIP EDIT (‘ [SEVERE] SUBSCRIPTRANGE ERROR DETECTED ‘)
(A);
PUT SKIP EDIT (‘ OCCURRED AT SUBSCRIPT: ‘, ONSOURCE())
(A, F(10));
/ ここでダンプ取得や独自の異常終了処理を呼ぶ /
ERR-FLG = ‘1’B;
GOTO ERROR_ROUTINE;
END;

— 意図的に範囲外アクセスを引き起こすテストループ —
DO IDX = 1 TO 15;
WS-TBL(IDX) = IDX 100; / IDX=11以降で例外発生 /
IF ERR-FLG THEN LEAVE;
END;

PUT SKIP EDIT (‘NORMAL TERMINATION (SHOULD NOT REACH HERE)’) (A);
RETURN;

ERROR_ROUTINE:
PUT SKIP EDIT (‘ PROGRAM ABNORMALLY TERMINATED BY TRAP ‘) (A);
SIGNAL ERROR; / 意図的にシステムへ異常終了を通知 /

END SUBS_TEST;

このコードでは、`ON SUBSCRIPTRANGE` を宣言することで、配列の上限(10)を超えた瞬間(`IDX = 11` のとき)にプログラムがクラッシュするのを防ぎ、捕捉ブロックへ制御を移している。

4. マイグレーションとエッジケースの罠(DB2・CICS・パックデシマル)

JavaやC#、あるいはクラウド環境へのレガシーマイグレーションを計画する際、このPL/I特有の「動的境界チェック」の挙動が大きな壁として立ち塞がる。

① 埋め込みSQL(DB2)との連携におけるエッジケース

動的SQLやホスト構造体配列(Host Structure Array)を使用する際、DB2から返されたレコード件数が、PL/I側で定義した配列の最大サイズを超過するケースがある。
ここで `NOSUBSCRIPTRANGE` のままでいると、DB2のフェッチバッファが溢れ、ワーキングストレージの隣接領域に格納されていたポインタ変数が書き換わる。結果として、次の瞬間に関数ポインタのジャンプ先が狂い、`S0C4` や `S0C1` といった不可解なアベンドを引き起こす。
マイグレーション先のJava(JDBC)では `ArrayIndexOutOfBoundsException` として即座に検知されるものが、メインフレームのネイティブ環境では「数世代前の別の処理」で爆発するため、デバッグが極めて困難になるのだ。

② パックデシマル(COMP-3)の内部符号反転バグと添字の相関

レガシーコードでありがちなのが、ファイルや電文から読み込んだパックデシマルの異常値(例えば、符号ニブルの破損やゾーン誤り)を、そのまま配列の添字としてキャストして使用するケースである。
PL/Iでは `FIXED DECIMAL` から `FIXED BINARY` への暗黙の型変換が高速に行われるため、不正なデータがそのまま添字として評価されやすい。
`ON SUBSCRIPTRANGE` を有効にしておけば、こうしたデータ起因の不正アクセスであっても、データがメモリを破壊する「その瞬間」にトラップを掛け、どの入力レコード(何件目のキー)が原因だったのかを特定することが可能になる。

5. アーキテクトからの提言:移行期における品質担保の鉄則

これからPL/Iシステムのモダナイゼーションや、Java/C#等へのリライト・リプラットフォームを推進するアーキテクトに伝えたい。

レガシーコードの解析において、ソースコードを目で追うだけの「静的解析」には限界がある。どれほど経験豊富なプログラマであっても、ポインタベースト変数や複雑なオフセット計算が絡み合った巨大なPL/Iプログラムのメモリマップを完全に脳内再現することは不可能なのだ。

1. 開発・単体テスト環境では必ず `SUBSCRIPTRANGE` を常時有効化する。
2. CI/CDパイプラインやテスト自動化のフェーズで、境界違反の発生をゼロにするゲートを設ける。
3. マイグレーション時の仕様書がおざなりになっている部分こそ、この例外処理を一時的に埋め込んで「実際の稼働データがどのような境界値を使っているか」を観測する。

PL/Iの古い仕様や警告を「古いもの」として切り捨てるのではなく、その言語が持つ防衛機能を正しく理解し、使いこなすこと。それこそが、止まることが許されない基幹システムを守り抜く、我々システムアーキテクトの真の腕の見せ所である。

タイトルとURLをコピーしました