こんにちは、基幹システムの現場を長年支えてきたメインフレーム・アーキテクトの私だ。
夜中に突如として運用担当者から「バッチが落ちた! S0C7だ!」と叩き起こされる。メインフレームエンジニアにとって、この「S0C7(データ例外)」ほど、冷や汗をかかされるアベンドコードもないだろう。
特に、COBOLからPL/Iへ、あるいはレガシーなPL/Iコードの保守において、`FIXED DECIMAL`(パック10進数 / COMP-3)や`FIXED BINARY`が絡むデータ混入は、一歩間違えると原因特定に迷宮入りすることもある。
今日は、なぜこのS0C7が発生するのか、その言語仕様の裏側と、VSAMなどのレコード入出力時における罠、そして現場で役立つONユニットを使った防衛的プログラミングの極意を、実践的なコードを交えて徹底的に解説しよう。
—
1. なぜS0C7は起きるのか? ~PL/Iと固定小数点数の宿命~
S0C7(System Completion Code 0C7)の正体は、CPU(IBM Z)の演算ハードウェアが検知した「データ例外(Data Exception)」だ。
PL/Iにおいて、計算や比較などの算術演算を行う際、コンパイラはデータ型に応じたマシン語命令(`PACKED DECIMAL` を処理する `AP`, `SP`, `MP`, `DP` や、十進比較の `CP` など)を生成する。これらの命令は、処理対象のデータが「正しい符号と正しいゾーン(またはパックされた数字)」であることを厳密に要求する。
ここで、`FIXED DECIMAL`(いわゆる `PIC’999V99’` など)として定義された領域を思い出してほしい。これらは内部的にコンパクションされ、1バイトに2桁の数字と、最下位ニブル(右側4ビット)に符号(`C`, `D`, `F` など)が格納される。
もし、外部ファイル(VSAMや順次ファイル)のレイアウト変更漏れ、あるいは不正な外部入力によって、このパック領域に「スペース(X’40’)」や「文字(A〜F以外のゴミデータ)」が混入した状態で算術演算やMOVEが行われると、CPUは「これ、数字として計算できねぇよ!」と悲鳴を上げ、それがS0C7となってバッチを強制終了させるのだ。
—
2. 現場でよくある発生パターンの解剖
実務で最も多いのは、「文字データ(CHARACTER)」から「固定小数点数(FIXED DECIMAL / BINARY)」への暗黙の型変換、あるいは古いVSAMファイルからの読み込み時の不整合だ。
例えば、以下のようなケースを考えてみてほしい。
レガシーな電文やCSVをフラットファイルとして受け取り、そのまま数値項目にバインドしようとした際、空白やハイフンが混ざっているだけで、PL/Iは容赦なくS0C7を発生させる。COBOLであればMOVE時にパディングやゾーンの補正をしてくれるケースもあるが、PL/I(特に最適化コンパイラ)はハードウェア命令に直結するコードを吐くため、データが汚れていると一発で落ちる。
—
3. 実践:ONユニットによる例外のトラップと制御フロー
「ファイルから変なデータが来たら、即座にバッチ全体をアベンドさせずに、エラーログを出して該当レコードをスキップしたい」――これが現場の切実な要望だろう。
PL/Iには、他言語にはない強力な例外処理機構である「ONユニット(On-Unit)」が存在する。`CONVERSION` 条件をトラップすることで、S0C7の直前で処理をフックし、ソフト異常としてハンドリングすることが可能だ。
以下のサンプルコードを見てほしい。大文字で記述された、実務でそのまま使える堅牢なPL/Iプログラムの構造だ。
DCL: FIXED DECIMAL と ON ユニットを活用した安全なデータ処理;
CONV_TEST: PROC OPTIONS(MAIN);
DCL IN_FILE FILE RECORD INPUT;
DCL OUT_FILE FILE RECORD OUTPUT;
— 入力レコードの構造定義(外部から文字として読み込む);
DCL 1 IN_RECORD,
5 EMP_ID CHAR(5),
5 EMP_NAME CHAR(20),
5 RAW_SALARY CHAR(7); — 汚れが含まれているかもしれない生データ;
— 演算用の正確な固定小数点数定義;
DCL SAFE_SALARY FIXED DEC(7,2);
DCL EOF_FLAG BIT(1) INIT(‘0’B);
DCL ERROR_COUNT FIXED BIN(31) INIT(0);
OPEN FILE(IN_FILE) INPUT, FILE(OUT_FILE) OUTPUT;
— CONVERSION条件(データ変換エラー)のトラップ設定;
ON CONVERSION BEGIN;
DISPLAY(‘【警告】数値変換エラーを検知しました。該当データをスキップします。’);
DISPLAY(‘ 問題のデータ(RAW_SALARY): ‘ || RAW_SALARY);
— エラーフラグを立てるか、デフォルト値を代入して処理を継続させる;
SAFE_SALARY = 0;
ERROR_COUNT = ERROR_COUNT + 1;
— GOTOでループの継続点へジャンプ(※ONユニット内からの制御移動);
GOTO READ_NEXT;
END;
— メイン処理ループ;
DO WHILE (EOF_FLAG = ‘0’B);
READ FILE(IN_FILE) INTO(IN_RECORD);
IF ENDFILE(IN_FILE) THEN DO;
EOF_FLAG = ‘1’B;
LEAVE;
END;
— ここで文字型からFIXED DECIMALへ代入(データが不正ならONユニットが発動);
SAFE_SALARY = RAW_SALARY;
— 正常な場合のビジネスロジック(例:給与にボーナス係数を掛ける);
- FIXED BINARYとDECIMALの混在演算では精度に注意する;
SAFE_SALARY = SAFE_SALARY 1.05;
— 出力処理…
WRITE FILE(OUT_FILE) FROM(IN_RECORD);
READ_NEXT:; — CONVERSION発生時のジャンプ先ラベル;
END;
CLOSE FILE(IN_FILE), FILE(OUT_FILE);
DISPLAY(‘処理完了。エラー件数 = ‘ || TRIM(ERROR_COUNT));
END CONV_TEST;
このコードのポイント
1. `ON CONVERSION BEGIN … END;`
文字型から数値型への暗黙の型変換や、パック10進数への代入時にデータ例外の予兆(不正文字)を検知すると、OSの強制アベンド(S0C7)の前にこのブロックが割り込む。
2. 安全なリカバリ
ONユニット内で `SAFE_SALARY = 0;` のようにフォールバック値を代入し、ラベルへGOTOさせることで、バッチ全体が墜落するのを防いでいる。
3. ビルトイン関数の活用
文字列のトリムや状態判定には `TRIM` などのPL/Iビルトイン関数を適切に組み合わせることで、堅牢性が飛躍的に向上する。
—
4. デバッグの現場から:アベンド時のポインタ解析のコツ
もし、ONユニットを仕掛け忘れて、本番運用中に見事にS0C7を踏んでしまった場合。SYSUDUMPやCEEDUMPを手元に、私たちはどのように原因を突き止めるべきか。
1. PSW(Program Status Word)の確認
ダンプリストにあるPSWのインストラクション・アドレス(命令アドレス)を確認する。これにより、どのソース行、あるいはどのマシン語命令(`AP`や`ZAP`など)の実行瞬間に例外が起きたかが特定できる。
2. レジスタとストレージの逆引き
問題の命令が指しているベース・レジスタを辿り、該当メモリスプレッド(ストレージ・ダンプ)をHEX(16進数)で覗く。
- 正しいパックデータであれば、下位ニブルは `C`, `D`, `F` のいずれかになっているはずだ。
- もしそこに `40404040`(スペース)や、漢字コードの一部、あるいはゴミが混入していれば、それが犯人である。
3. コンパイラオプションの確認
開発・テスト環境では、コンパイルオプションに `TEST` や `CHECK` を付与し、サブスクリプト範囲外やデータ例外を詳細にトラップできるようにしておくことが、プロとしての最低限のたしなみだ。
—
5. アーキテクトからの提言
固定小数点数(`FIXED BINARY` / `FIXED DECIMAL`)とS0C7の関係は、突き詰めれば「型とデータの契約違反」に他ならない。
マイグレーションやリプレイスの現場では、「昔のファイル構造だから大丈夫」という思い込みが一番の毒になる。外部から流入するデータは常に汚れているという「ゼロ・トラスト」の精神を持ち、入出力の境界線上では必ず文字型として受け取り、バリデーション(検証)を行ってから安全に数値型へ変換する、あるいは今回紹介したONユニットを適切に配置する。
この一手間を惜しまないコードこそが、夜中に呼び出されない、真に信頼性の高いメインフレームシステムを作り上げるのだ。
さあ、今日の保守作業に戻るとしよう。エラーログの出力フォーマットの確認を忘れるなよ!
