Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 1002] errchkスクリプトのBUGメッセージ重複出力修正

コミット

  • コミットハッシュ: eb5a316fa6e5a12d10f7054ff3d9de608d772278
  • 作成者: Russ Cox rsc@golang.org
  • 作成日: 2008年10月30日 12:43:32 (PDT)
  • メッセージ: make sure errchk only prints BUG once. using a variable is not sufficient, because sometimes bug() is called from a subshell.

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/eb5a316fa6e5a12d10f7054ff3d9de608d772278

元コミット内容

commit eb5a316fa6e5a12d10f7054ff3d9de608d772278
Author: Russ Cox <rsc@golang.org>
Date:   Thu Oct 30 12:43:32 2008 -0700

    make sure errchk only prints BUG once.
    using a variable is not sufficient, because
    sometimes bug() is called from a subshell.
    
    R=iant
    DELTA=7  (2 added, 1 deleted, 4 changed)
    OCL=18092
    CL=18145
---
 test/errchk | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/test/errchk b/test/errchk
index a8476a6258..2b602c3c7f 100755
--- a/test/errchk
+++ b/test/errchk
@@ -29,9 +29,11 @@ TMPERR=/tmp/errchk-err-$$
 TMPALL=/tmp/errchk-all-$$
 TMPTMP=/tmp/errchk-tmp-$$
 TMPSTAT=/tmp/errchk-stat-$$
-rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT
+TMPBUG=/tmp/errchk-bug-$$
 
-trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT" 0 1 2 3 14 15
+rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG
+
+trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG" 0 1 2 3 14 15
 
 if $* >$TMPOUT 2>$TMPERR; then
   echo 1>&2 "BUG: errchk: command succeeded unexpectedly: " "$@"
@@ -43,12 +45,11 @@ fi
 
 cat $TMPOUT $TMPERR | grep -v '^	' > $TMPALL
 
-didbug=false
 bug() {
-  if ! $didbug
+  if ! test -f $TMPBUG
   then
     echo 1>&2 -n BUG: ''
-    didbug=true
+    echo >$TMPBUG
   fi
 }

変更の背景

このコミットは、Goコンパイラの初期開発段階(2008年)において、テストツールerrchkの重要なバグ修正を行っています。Go言語プロジェクトは2007年9月21日にRobert Griesemer、Rob Pike、Ken Thompsonによって開始され、2008年1月にはKen Thompsonが最初のコンパイラ開発を開始していました。

当時のGoコンパイラはまだ実験的な段階にあり、C言語への変換を行うプロトタイプコンパイラとして動作していました。この時期には、コンパイラの動作確認やエラーメッセージの検証のために、様々なテストツールが必要でした。errchkはその中でも重要な役割を果たしており、コンパイラが出力するエラーメッセージが期待通りであることを確認するために使用されていました。

問題の発生背景:

  • errchkツールは、Goコンパイラのエラーメッセージを検証するためのシェルスクリプトでした
  • このツールは、テストケースで発生するエラーが期待されるパターンと一致するかを確認する重要な役割を担っていました
  • しかし、複数回の"BUG"メッセージが出力される問題が発生していました
  • 特に、サブシェルからbug()関数が呼び出される際に、変数による重複防止機能が正常に動作しない問題がありました

前提知識の解説

errchkツールについて

errchkは、Goコンパイラの初期開発において使用されていたシェルスクリプトベースのテストツールです。このツールは以下の機能を提供していました:

  1. エラーメッセージの検証: コンパイラが出力するエラーメッセージが期待されるパターンと一致するかをチェック
  2. テストの自動化: コンパイラテストの実行とエラー報告を自動化
  3. 回帰テストの実行: 新しい変更がコンパイラの動作に悪影響を与えていないかを検証

シェルスクリプトでの変数スコープ問題

シェルスクリプトにおいて、サブシェルで実行される関数は親シェルの変数を変更できません。これは、サブシェルが独立したプロセス空間で動作するためです。例えば:

flag=false
(
    flag=true  # サブシェル内での変更
)
echo $flag  # 依然として false

この特性により、didbug変数を使用したBUGメッセージの重複防止機能が、サブシェルからbug()関数が呼び出される場合に正しく動作しませんでした。

一時ファイルによる状態管理

シェルスクリプトで複数のプロセス間で状態を共有する際の一般的な解決策として、一時ファイルを使用する方法があります:

  • プロセス間通信: 複数のプロセスが同じファイルシステムを参照することで状態を共有
  • 原子操作: ファイルの作成・削除は原子的操作として扱われる
  • 持続性: プロセスが終了しても状態が保持される

2008年のGo開発環境

このコミットが作成された2008年は、Go言語の開発初期段階でした:

  • Goはまだ公開されておらず、Googleの内部プロジェクトとして開発中
  • Robert Griesemer、Rob Pike、Ken Thompsonらが中心となって言語設計を進めていた
  • Russ Coxは、コンパイラとランタイムシステムの実装を担当していた
  • テストインフラストラクチャも同時に構築されていた時期
  • 最初のGoプログラムが2008年2月6日に動作し、Ken Thompsonが最初のコンパイラを開発していた

技術的詳細

修正前の実装(変数ベース)

didbug=false
bug() {
  if ! $didbug
  then
    echo 1>&2 -n BUG: ''
    didbug=true
  fi
}

この実装では、didbug変数を使用してBUGメッセージが既に出力されたかどうかを記録していました。しかし、bug()関数がサブシェルから呼び出された場合、以下の問題が発生しました:

  1. スコープの分離: サブシェルでのdidbug=trueは親シェルの変数に影響しない
  2. 状態の非同期: 複数のサブシェルが同時に実行される場合、それぞれが独立したdidbug変数を持つ
  3. 重複出力: 各サブシェルで最初のbug()呼び出し時にBUGメッセージが出力される

修正後の実装(ファイルベース)

TMPBUG=/tmp/errchk-bug-$$
rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG

trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG" 0 1 2 3 14 15

bug() {
  if ! test -f $TMPBUG
  then
    echo 1>&2 -n BUG: ''
    echo >$TMPBUG
  fi
}

この修正では、一時ファイル$TMPBUGを使用してBUGメッセージの出力状態を管理しています:

  1. プロセス間での状態共有: ファイルシステムを通じて全てのプロセスが同じ状態を参照
  2. 原子的な状態確認: test -fコマンドによるファイル存在チェック
  3. 適切なクリーンアップ: trapによる一時ファイルの確実な削除

問題の具体的な発生パターン

# 例:サブシェルでの実行パターン
(
    # サブシェル内で実行
    some_command_that_triggers_bug_function
    bug "エラーメッセージ"
)

この場合、親シェルで設定された「既に"BUG"を出力した」というフラグ変数がサブシェルに継承されないため、重複出力が発生していました。

コアとなるコードの変更箇所

1. 一時ファイル変数の追加

 TMPSTAT=/tmp/errchk-stat-$$
-rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT
+TMPBUG=/tmp/errchk-bug-$$
 
-trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT" 0 1 2 3 14 15
+rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG
+
+trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG" 0 1 2 3 14 15

2. bug()関数の修正

-didbug=false
 bug() {
-  if ! $didbug
+  if ! test -f $TMPBUG
   then
     echo 1>&2 -n BUG: ''
-    didbug=true
+    echo >$TMPBUG
   fi
 }

コアとなるコードの解説

一時ファイル名の生成

TMPBUG=/tmp/errchk-bug-$$
  • $$は現在のプロセスID(PID)を表す特殊変数
  • 複数のerrchkインスタンスが同時実行されても、PIDが異なるため一意なファイル名が生成される
  • /tmp/errchk-bug-1234のような形式になる

ファイル初期化とクリーンアップ

rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG
  • -fオプションにより、ファイルが存在しない場合でもエラーにならない
  • スクリプト開始時に古い一時ファイルを確実に削除

trapによるクリーンアップ

trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG" 0 1 2 3 14 15
  • trapコマンドは、指定されたシグナルを受信した際にコマンドを実行
  • 0: 正常終了時
  • 1: SIGHUP(ハングアップ)
  • 2: SIGINT(割り込み、通常Ctrl+C)
  • 3: SIGQUIT(終了)
  • 14: SIGALRM(アラーム)
  • 15: SIGTERM(終了要求)

状態確認と更新

if ! test -f $TMPBUG
then
    echo 1>&2 -n BUG: ''
    echo >$TMPBUG
fi
  • test -f $TMPBUG: ファイルが存在し、かつ通常ファイルかどうかをチェック
  • echo >$TMPBUG: 空のファイルを作成(既存の場合は空にする)
  • echo 1>&2 -n BUG: '': 標準エラー出力に改行なしで"BUG: "を出力

修正の技術的意義

  1. プロセス間での状態共有: ファイルシステムを使用した状態管理により、サブシェルとの状態共有を実現
  2. 堅牢性の向上: 変数ベースの不安定な解決策から、より確実な方法への移行
  3. テストの信頼性向上: 重複出力の排除により、テスト結果の解析が容易になる

関連リンク

参考にした情報源リンク