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

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

本コミットは、Goコンパイラのcmd/gcパッケージにおけるwidstruct関数の挙動を改善し、無効なフィールド(TFIELD)に対するより堅牢なハンドリングを導入するものです。これにより、特定の状況下で発生していたセグメンテーション違反(segfaults)や、test/fixedbugs/bug365.goのようなテストケースの失敗を防ぐことを目的としています。

コミット

commit caff4398203582dcdfe400b8b27c9ce218ea4564
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Sun Oct 7 21:46:10 2012 +0200

    cmd/gc: more graceful handling of invalid fields in widstruct.
    
    The protection against segfaults does not completely solve
    crashes and breaks test/fixedbugs/bug365.go
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6615058

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

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

元コミット内容

cmd/gc: more graceful handling of invalid fields in widstruct. The protection against segfaults does not completely solve crashes and breaks test/fixedbugs/bug365.go

変更の背景

このコミットの背景には、Goコンパイラの型システムとメモリレイアウト計算における既存の脆弱性がありました。具体的には、widstruct関数が構造体のフィールドの幅(メモリサイズ)を計算する際に、無効な(T型の)フィールドに遭遇した場合に、セグメンテーション違反を引き起こす可能性がありました。

以前の修正では、セグメンテーション違反に対する保護が導入されていましたが、それが完全ではなく、test/fixedbugs/bug365.goという特定のテストケースが引き続きクラッシュするか、期待通りに動作しない問題が残っていました。このテストケースは、おそらく不正な型定義や特殊な構造体レイアウトを意図的に含んでおり、コンパイラの堅牢性を試すものであったと推測されます。

本コミットは、この残存する問題を解決し、コンパイラが不正な型定義に遭遇した場合でも、クラッシュすることなく、少なくとも有効なフィールドの幅計算を続行できるようにするためのものです。

前提知識の解説

このコミットを理解するためには、以下のGoコンパイラに関する基本的な知識が必要です。

  • Goコンパイラ (cmd/gc): Go言語の公式コンパイラであり、Goソースコードを機械語に変換する役割を担います。gcは"Go compiler"の略です。
  • 型システム: Go言語におけるデータ型(int, string, structなど)の定義、検証、および操作に関する規則の集合です。コンパイラは、プログラムの型が正しく使用されているかを検証します。
  • 構造体 (struct): 複数の異なる型のフィールドをまとめた複合データ型です。Goでは、構造体のフィールドはメモリ上で連続して配置されることが一般的です。
  • メモリレイアウトとアライメント: プログラムのデータがメモリ上でどのように配置されるか、および特定のデータ型がメモリ上で特定の境界(アライメント)に配置される必要があるという規則です。コンパイラは、構造体の全体のサイズ(幅)と各フィールドのオフセットを計算する際に、これらの規則を考慮します。
  • widstruct関数: Goコンパイラの内部関数の一つで、構造体(struct)のメモリ上の幅(サイズ)を計算する役割を担います。この関数は、構造体の各フィールドの型とアライメント要件を考慮して、構造体全体のサイズを決定します。
  • Type構造体: Goコンパイラの内部で型情報を表現するために使用されるデータ構造です。Type構造体には、型の種類(etype)、フィールドのリスト(downポインタ)、アライメント情報などが含まれます。
  • TFIELD: Type構造体のetypeフィールドが取りうる値の一つで、そのTypeが構造体のフィールドを表すことを示します。
  • T: Goコンパイラの内部で、無効な型や未解決の型を表すために使用される特殊な型です。これは、型解決の失敗や、不正な型定義があった場合に設定されることがあります。
  • セグメンテーション違反 (segfault): プログラムがアクセスを許可されていないメモリ領域にアクセスしようとしたときに発生するエラーです。これは通常、ポインタの不正な使用や、無効なメモリ参照によって引き起こされます。コンパイラのような複雑なソフトウェアでは、内部データ構造の破損が原因で発生することがあります。
  • test/fixedbugs/bug365.go: Goのテストスイートに含まれる、特定のバグを再現し、その修正を検証するためのテストケースです。このテストが失敗するということは、コンパイラにまだ問題が残っていることを示します。

技術的詳細

本コミットは、src/cmd/gc/align.c内のwidstruct関数と、src/cmd/gc/go.h内のType構造体の定義に焦点を当てています。

widstruct関数は、与えられた構造体型tの各フィールドfをイテレートし、それぞれのフィールドの幅を計算(dowidth(f->type))し、構造体全体の幅とアライメントを決定します。

変更前のコードでは、フィールドfの型がT(無効な型)である場合、if(f->type == T) break;という条件でループを中断していました。これは、無効なフィールドに遭遇した場合にそれ以上処理を進めないことで、セグメンテーション違反を防ぐ意図があったと考えられます。しかし、このアプローチでは、無効なフィールドの後に続く有効なフィールドの幅が計算されず、結果として構造体全体の幅が正しく計算されない、あるいは他の問題を引き起こす可能性がありました。コミットメッセージにある「The protection against segfaults does not completely solve crashes and breaks test/fixedbugs/bug365.go」という記述は、このbreakによる早期終了が、完全な解決策ではなかったことを示唆しています。

本コミットでは、このbreak文をcontinue文に変更しています。 if(f->type == T) continue; この変更により、widstruct関数は無効なフィールド(f->type == T)に遭遇した場合でも、ループを中断せずに次のフィールドの処理に進むようになります。コメント// broken field, just skip it so that other valid fields // get a width.が示すように、これは「壊れたフィールドはスキップし、他の有効なフィールドが幅を得られるようにする」という意図に基づいています。これにより、コンパイラは不正な型定義を含む構造体に対しても、可能な限り堅牢に処理を続行し、クラッシュを回避しつつ、少なくとも有効な部分の型情報を構築できるようになります。

また、src/cmd/gc/go.hでは、Type構造体のuchar broke;フィールドにコメントが追加されています。 uchar broke; // broken type definition. これは、このbrokeフィールドが「壊れた型定義」を示すために使用されることを明示しています。このフィールドは、型解決の過程で問題が発生した場合に設定され、コンパイラがその型が不完全または不正であることを認識するためのフラグとして機能します。widstruct関数でのT型チェックと合わせて、コンパイラが不正な型定義をより適切に識別し、処理するためのメカニズムの一部を構成しています。

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

src/cmd/gc/align.c

--- a/src/cmd/gc/align.c
+++ b/src/cmd/gc/align.c
@@ -54,8 +54,11 @@ widstruct(Type *errtype, Type *t, vlong o, int flag)
 	for(f=t->type; f!=T; f=f->down) {
 		if(f->etype != TFIELD)
 			fatal("widstruct: not TFIELD: %lT", f);
-		if(f->type == T)
-			break;
+		if(f->type == T) {
+			// broken field, just skip it so that other valid fields
+			// get a width.
+			continue;
+		}
 		dowidth(f->type);
 		if(f->type->align > maxalign)
 			maxalign = f->type->align;

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -146,7 +146,7 @@ struct	Type
 	uchar	copyany;\n 	uchar	local;\t\t// created in this file
 	uchar	deferwidth;
-	uchar	broke;
+	uchar	broke;  	// broken type definition.
 	uchar	isddd;\t\t// TFIELD is ... argument
 	uchar	align;

コアとなるコードの解説

src/cmd/gc/align.cの変更

widstruct関数内のループ処理が変更されています。

  • 変更前:

    if(f->type == T)
        break;
    

    構造体のフィールドfの型がT(無効な型)である場合、ループをbreak(中断)していました。これにより、無効なフィールド以降のフィールドは処理されませんでした。

  • 変更後:

    if(f->type == T) {
        // broken field, just skip it so that other valid fields
        // get a width.
        continue;
    }
    

    型がTである場合でも、ループをcontinue(次のイテレーションへスキップ)するようになりました。これにより、無効なフィールドはスキップされますが、その後に続く有効なフィールドの幅計算は続行されます。この変更は、コンパイラが部分的に不正な型定義を持つ構造体に対しても、可能な限り多くの情報を抽出し、クラッシュを回避するための堅牢性向上策です。

src/cmd/gc/go.hの変更

Type構造体のbrokeフィールドに対するコメントが追加されました。

  • 変更前: uchar broke;
  • 変更後: uchar broke; // broken type definition. このコメント追加は、brokeフィールドの目的を明確にするものです。このフィールドは、型定義が何らかの理由で「壊れている」状態、つまり不完全または不正であることを示すフラグとして機能します。これは、コンパイラが内部的に型の健全性を追跡し、エラーハンドリングを改善するためのメタデータです。

これらの変更は、Goコンパイラがより複雑で、時には不正な入力に対しても、より回復力を持って動作するようにするための重要な改善です。

関連リンク

参考にした情報源リンク

  • Go言語のコミット履歴と関連するコードレビュー(golang.org/cl/6615058
  • Goコンパイラのソースコード(src/cmd/gc/align.c, src/cmd/gc/go.h
  • Go言語のドキュメントおよびGoコンパイラの内部構造に関する一般的な知識
  • セグメンテーション違反に関する一般的なプログラミング知識
  • test/fixedbugs/bug365.goに関する情報(直接参照はしていませんが、コミットメッセージからその存在と役割を推測しました)
  • Go言語のIssueトラッカー(関連するバグ報告や議論がある可能性)
  • Go言語のメーリングリスト(golang-dev)のアーカイブ(関連する議論がある可能性)
  • Goコンパイラの内部に関するブログ記事や解説記事(一般的な情報収集のため)