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

[インデックス 17824] ファイルの概要

このコミットは、Go言語のcmd/yaccツールにおけるWindows環境での標準エラー出力(stderr)の挙動を修正するものです。具体的には、stderrへの書き込みがWindowsで正しく機能しない問題を解決しています。

コミット

commit be1a94b401112cab46a60c8bb9c42e16e1b70647
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sat Oct 19 23:07:20 2013 -0400

    cmd/yacc: fix stderr on Windows.
    Fixes #6620.
    
    R=golang-dev, dave, r
    CC=golang-dev
    https://golang.org/cl/15330043

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

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

元コミット内容

cmd/yacc: fix stderr on Windows. Fixes #6620.

このコミットは、cmd/yaccツールがWindows上で標準エラー出力にメッセージを正しく出力できない問題を修正します。関連するイシューは #6620 です。

変更の背景

Go言語のcmd/yaccツールは、Yacc(Yet Another Compiler Compiler)のGo実装であり、構文解析器(パーサー)を生成するために使用されます。このツールは、コンパイル時や実行時にエラーメッセージや警告を標準エラー出力(stderr)に書き出すことが一般的です。

しかし、Windows環境において、cmd/yaccstderrにメッセージを出力しようとした際に、その出力が正しく表示されない、あるいは全く出力されないという問題が発生していました。これは、stderrを扱うための内部的な実装が、Windowsのファイルディスクリプタの扱いに適していなかったためと考えられます。

具体的には、Goの標準ライブラリで提供されるos.NewFile(fd, name)関数を使用してファイルディスクリプタ2(stderrに対応)を直接開く方法が、Windowsのコンソールアプリケーションのstderrの挙動と完全に互換性がなかったことが原因です。Unix系システムではファイルディスクリプタがより直接的に扱えるのに対し、WindowsではコンソールI/Oの扱いが異なるため、このような問題が発生することがあります。

この問題は、ツールのデバッグやエラー診断を困難にし、ユーザーエクスペリエンスを低下させるものでした。そのため、Windows環境でのstderrの正しい動作を保証するための修正が必要とされました。

前提知識の解説

Yacc (Yet Another Compiler Compiler)

Yaccは、プログラミング言語のコンパイラやインタプリタの構文解析部分(パーサー)を自動生成するためのツールです。BNF(Backus-Naur Form)に似た文法定義ファイルを受け取り、その文法に従って入力ストリームを解析するC言語(または他の言語)のコードを生成します。Go言語のcmd/yaccは、このYaccの概念をGo言語で実装したものです。

標準ストリーム (Standard Streams)

オペレーティングシステムにおいて、プログラムは通常、以下の3つの標準ストリームを通じて入出力を行います。

  1. 標準入力 (stdin): プログラムがデータを読み込むための入力ストリーム。通常はキーボードに接続されています。ファイルディスクリプタは0。
  2. 標準出力 (stdout): プログラムが通常の出力データを書き込むための出力ストリーム。通常はコンソール(画面)に接続されています。ファイルディスクリプタは1。
  3. 標準エラー出力 (stderr): プログラムがエラーメッセージや診断情報を書き込むための出力ストリーム。通常はコンソール(画面)に接続されていますが、stdoutとは独立しており、リダイレクトなどによってstdoutとは異なる場所に送ることができます。ファイルディスクリプタは2。

ファイルディスクリプタ (File Descriptor)

ファイルディスクリプタは、Unix系オペレーティングシステムにおいて、開かれたファイルやI/Oデバイス(ソケット、パイプなどを含む)を参照するために使用される抽象的なインデックス(整数値)です。各プロセスは、開いているファイルディスクリプタのテーブルを保持しています。標準ストリーム(stdin, stdout, stderr)は、それぞれ0, 1, 2という特定のファイルディスクリプタ番号に割り当てられています。

Windowsでは、ファイルディスクリプタの概念はUnix系システムとは異なりますが、Cランタイムライブラリなどを通じて同様の抽象化が提供されることがあります。しかし、Go言語のような高レベルな言語で直接ファイルディスクリプタを扱う場合、OSごとの違いを考慮する必要があります。

os.NewFileos.Stderr

  • os.NewFile(fd uintptr, name string) *File: この関数は、指定されたファイルディスクリプタfdと名前nameを持つ新しいos.Fileオブジェクトを作成します。これは、既存のOSレベルのファイルディスクリプタをGoの*os.Fileオブジェクトにラップするために使用されます。
  • os.Stderr *File: これはGoのosパッケージで定義されているグローバル変数で、プログラムの標準エラー出力に対応する*os.Fileオブジェクトを指します。Goランタイムが起動する際に、OSの標準エラー出力に適切に接続されるように初期化されます。

問題は、os.NewFile(2, "stderr")のようにファイルディスクリプタ2を直接指定してstderrを再構築しようとすると、特にWindows環境において、Goランタイムが既に確立しているos.Stderrの内部的な状態や、WindowsのコンソールI/Oの特性との間に不整合が生じる可能性があったことです。os.StderrはGoランタイムによってOSの特性を考慮して適切に初期化されているため、これを使用する方がクロスプラットフォームで安全かつ確実な方法です。

技術的詳細

このコミットの技術的な核心は、Goプログラムが標準エラー出力にアクセスする方法の変更にあります。

変更前は、cmd/yaccツール内でstderrを扱うために、以下のようなコードが使用されていました。

stderr = bufio.NewWriter(os.NewFile(2, "stderr"))

この行は、ファイルディスクリプタ2(Unix系システムではstderrに対応)を直接指定して新しいos.Fileオブジェクトを作成し、それをbufio.NewWriterでラップしてバッファリングされたライターとしてstderr変数に割り当てていました。

このアプローチは、Unix系システムでは一般的に機能しますが、Windowsでは問題がありました。WindowsのコンソールI/Oは、Unixのファイルディスクリプタモデルとは異なる内部的なメカニズムを持っています。os.NewFile(2, "stderr")のように直接ファイルディスクリプタ番号を指定してstderrを「再オープン」しようとすると、Windowsのコンソールハンドルの正しい取得や、Goランタイムが内部的に管理しているstderrの状態との整合性が取れなくなる可能性がありました。結果として、stderrへの書き込みが正しくコンソールに表示されない、あるいはエラーになるという挙動を引き起こしていました。

変更後は、以下のコードに修正されました。

stderr = bufio.NewWriter(os.Stderr)

この変更により、os.NewFile(2, "stderr")を介してstderrを明示的に再構築する代わりに、Goのosパッケージが提供するグローバル変数os.Stderrを直接使用するようになりました。os.Stderrは、Goランタイムが起動する際に、オペレーティングシステムの特性(WindowsのコンソールI/Oを含む)を考慮して適切に初期化され、標準エラー出力に接続された*os.Fileオブジェクトを指します。

この修正の利点は以下の通りです。

  1. クロスプラットフォーム互換性: os.Stderrを使用することで、Goランタイムが各OSのstderrの扱いの違いを吸収してくれるため、開発者はOS固有のファイルディスクリプタの挙動を意識する必要がなくなります。これにより、Windowsを含むすべてのサポート対象プラットフォームでstderrが確実に機能するようになります。
  2. 堅牢性: os.StderrはGoランタイムによって管理されており、そのライフサイクルや内部状態はGoのI/Oシステムと密接に統合されています。直接ファイルディスクリプタを操作するよりも、より堅牢でエラーが発生しにくいアプローチです。
  3. 簡潔性: コードがより簡潔になり、意図が明確になります。

この修正は、Goの標準ライブラリの設計思想、すなわち「OSの差異を吸収し、開発者に統一されたインターフェースを提供する」という原則に沿ったものです。

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

変更は src/cmd/yacc/yacc.go ファイルの1箇所のみです。

--- a/src/cmd/yacc/yacc.go
+++ b/src/cmd/yacc/yacc.go
@@ -357,7 +357,7 @@ func main() {
 func setup() {
 	var j, ty int
 
-	stderr = bufio.NewWriter(os.NewFile(2, "stderr"))
+	stderr = bufio.NewWriter(os.Stderr)
 	foutput = nil
 
 	flag.Parse()

コアとなるコードの解説

src/cmd/yacc/yacc.go 内の setup() 関数は、cmd/yaccツールの初期設定を行う部分です。この関数内で、標準エラー出力にメッセージを書き込むためのstderr変数が初期化されています。

変更前のコード stderr = bufio.NewWriter(os.NewFile(2, "stderr")) は、ファイルディスクリプタ2(stderr)を直接指定して新しいファイルオブジェクトを作成し、それをバッファリングされたライターとして使用していました。前述の通り、この方法はWindows環境で問題を引き起こしていました。

変更後のコード stderr = bufio.NewWriter(os.Stderr) は、Goのosパッケージが提供する組み込みのos.Stderrオブジェクトを直接使用しています。os.Stderrは、Goプログラムが起動した際に、オペレーティングシステムが提供する標準エラー出力ストリームに適切に接続されるようにGoランタイムによって初期化されます。これにより、Windowsを含むすべてのプラットフォームでstderrへの出力が確実に機能するようになります。

この修正は、cmd/yaccがエラーメッセージや警告をユーザーに正しく伝えるために不可欠であり、特にWindowsユーザーにとってツールの使いやすさを向上させました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のGitHubリポジトリのコミット履歴
  • 一般的なオペレーティングシステムの標準ストリームとファイルディスクリプタに関する知識