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

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

このコミットは、Go言語のCgoツールにおける型キャストの生成方法に関するバグ修正です。具体的には、CgoがGoの関数からCの関数を呼び出す際に生成するCコードにおいて、戻り値の型キャストが不適切であった問題を解決します。__typeof__ GCC拡張機能を使用することで、より正確な型推論に基づいたキャストが行われるようになります。

コミット

commit 5b097e79511abb83ef04401140d7dab2b42c41a3
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sun Jun 2 22:46:53 2013 +0800

    cmd/cgo: using __typeof__(a->r) instead of putting invalid TYPE in "a->r = (const TYPE)"
    Thanks kballard for the hint.
    Fixes #4857.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/9649045

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

https://github.com/golang/go/commit/5b097e79511abb83ef04401140d7dab2b42c41a3

元コミット内容

このコミットは、Cgoが生成するCコードにおいて、Goの型をCの型にキャストする際に発生していた問題を修正します。以前は a->r = (const TYPE) のように、Goの型情報から推測したCの型(TYPE)を用いてキャストを行っていましたが、これが特定の状況下で無効な型を生成し、コンパイルエラーを引き起こす可能性がありました。この修正では、代わりに __typeof__(a->r) を使用することで、a->r の実際の型に基づいてキャストを行うように変更されています。この変更は、kballard氏からのヒントに基づき、Issue #4857 を修正するものです。

変更の背景

Go言語はCgoというツールを提供しており、これによりGoプログラムからC言語のコードを呼び出すことができます。Cgoは、GoとCの間のインターフェースとなるCコードを自動生成します。この生成されるCコードは、Goの型とCの型の間で適切にデータを変換(キャスト)する必要があります。

このコミットが修正する問題は、CgoがGoの関数からCの関数を呼び出す際の戻り値の型キャストにありました。特に、const修飾子が付いた型や、より複雑なポインタ型の場合に、Cgoが生成する (const TYPE) のような明示的なキャストが、Cコンパイラにとって無効な型指定となることがありました。これにより、Cgoで生成されたコードがコンパイルエラーとなる事態が発生していました。

misc/cgo/test/issue4857.go に追加されたテストケースは、この問題を具体的に示しています。このテストでは、const struct { int a; } * というCの型を返す関数をGoから呼び出す際に、-Werror フラグ(警告をエラーとして扱う)を付けてコンパイルするとエラーになることが示唆されています。これは、Cgoが生成するキャストがCコンパイラの型チェックを通過できなかったためと考えられます。

この問題は、CgoがGoの型システムとCの型システムの間で、より堅牢な型変換ロジックを必要としていることを浮き彫りにしました。

前提知識の解説

Cgo

Cgoは、GoプログラムがC言語のコードを呼び出すためのGoツールチェーンの一部です。Goのソースファイル内に特別なコメントブロック(import "C" の直前)でCコードを記述することで、GoとCの間の相互運用を可能にします。Cgoは、これらのCコードをコンパイルし、GoからC関数を呼び出すためのスタブ関数や、CからGo関数を呼び出すためのラッパー関数を自動生成します。

型キャスト

型キャストとは、あるデータ型を別のデータ型に明示的に変換することです。C言語では、(type)expression の形式で記述されます。例えば、(int)3.14 は浮動小数点数 3.14 を整数型 3 に変換します。型キャストは、異なる型の変数間で値を代入する際や、関数の引数として渡す際に、コンパイラに意図した型変換を指示するために使用されます。不適切な型キャストは、コンパイルエラーや未定義の動作を引き起こす可能性があります。

__typeof__ (GCC拡張)

__typeof__ は、GCC (GNU Compiler Collection) が提供するC言語の拡張機能です。これは、変数や式の型をコンパイル時に取得するために使用されます。例えば、__typeof__(a->r) は、式 a->r の型を返します。この機能は、ジェネリックなマクロや、型に依存しないコードを記述する際に非常に便利です。標準C言語には同様の機能はありませんが、多くのC/C++コンパイラが互換性のためにこの拡張をサポートしています。

このコミットでは、__typeof__ を使用することで、Cgoが生成するCコードが、Goの型情報から推測した抽象的なCの型ではなく、実際に代入される変数(a->r)の具体的な型に基づいてキャストを行うことができるようになります。これにより、より正確で堅牢な型変換が実現されます。

const 修飾子

const 修飾子は、C言語において変数が変更されないことを示すために使用されます。例えば、const int *p は、p が指す先の整数が変更できないことを意味します。const は型の一部と見なされ、型システムにおいて重要な役割を果たします。const の有無によって型が異なるため、const を適切に扱わない型キャストは、コンパイルエラーや警告の原因となることがあります。

技術的詳細

このコミットの核心は、CgoがGoの型をCの型に変換する際のロジックの改善にあります。Cgoは、GoのコードからCの関数を呼び出すためのCラッパーコードを src/cmd/cgo/out.go で生成します。特に、Goの関数がCの関数を呼び出し、その戻り値を受け取る部分で問題が発生していました。

以前のCgoのコードでは、Goの型 t に対応するCの型 t.C を取得し、その型を用いて戻り値 a->r をキャストしていました。具体的には、fmt.Fprintf(fgcc, "(const %s) ", t.C) のように、t.C をプレースホルダーとして使用していました。しかし、t.C が常にCコンパイラが期待する正確な型表現であるとは限りませんでした。特に、const 修飾子が付いたポインタ型や、構造体へのポインタなど、複雑な型の場合に問題が生じやすかったと考えられます。

例えば、Goのコードで C.issue4857() のようなC関数を呼び出す場合、CgoはGoの型システムから issue4857 の戻り値の型を推測し、それをCの型にマッピングします。しかし、このマッピングが常にCコンパイラの厳密な型チェック(特に -Werror のようなフラグが有効な場合)を通過するとは限りませんでした。

この修正では、この問題を解決するために __typeof__(a->r) を導入しました。a->r は、Cgoが生成するCコード内で、Goの関数呼び出しの戻り値を格納するための変数です。__typeof__(a->r) を使用することで、Cgoは a->r実際の 型をコンパイラに推論させ、その型に基づいてキャストを行うようになります。これにより、CgoがGoの型からCの型を「推測」する際の潜在的な不一致が解消され、Cコンパイラが期待する正確な型キャストが生成されるようになります。

この変更は、Cgoが生成するCコードの堅牢性を高め、GoとCの間の型変換における潜在的なコンパイルエラーを回避するために非常に重要です。

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

変更は src/cmd/cgo/out.go ファイルにあります。

--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -490,7 +490,7 @@ func (p *Package) writeOutputFunc(fgcc *os.File, n *Name) {
  	if t := n.FuncType.Result; t != nil {
  		fmt.Fprintf(fgcc, "a->r = ")
  		if c := t.C.String(); c[len(c)-1] == '*' {
- 			fmt.Fprintf(fgcc, "(const %s) ", t.C)
+ 			fmt.Fprint(fgcc, "(__typeof__(a->r)) ")
  		}
  	}
  	fmt.Fprintf(fgcc, "%s(\", n.C)

また、この変更に関連して、問題を再現するためのテストケースが misc/cgo/test/issue4857.go に追加されています。

--- /dev/null
+++ b/misc/cgo/test/issue4857.go
@@ -0,0 +1,15 @@
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cgotest
+
+/*
+#cgo CFLAGS: -Werror
+const struct { int a; } *issue4857() { return (void *)0; }
+*/
+import "C"
+
+func test4857() {
+	_ = C.issue4857()
+}

コアとなるコードの解説

src/cmd/cgo/out.gowriteOutputFunc 関数は、Goの関数呼び出しに対応するCのラッパー関数を生成する役割を担っています。この関数内で、Goの関数がCの関数を呼び出し、その戻り値を受け取る部分のコードが生成されます。

変更前のコード:

fmt.Fprintf(fgcc, "(const %s) ", t.C)

この行は、CgoがGoの型 t に対応するCの型 t.C を取得し、それを (const %s) の形式でCのキャストとして出力していました。t.C はGoの型システムからCの型表現に変換された文字列です。しかし、この t.C が常にCコンパイラが期待する正確な型表現であるとは限りませんでした。特に、const 修飾子が付いたポインタ型や、複雑な構造体へのポインタなど、Cの型システムが厳密に型を区別するような場合に、この生成されたキャストがコンパイルエラーを引き起こす可能性がありました。例えば、const struct S * のような型に対して、Cgoが struct S *t.C として生成し、それに const を付加しようとすると、Cコンパイラが構文エラーや型不一致のエラーを出すことがありました。

変更後のコード:

fmt.Fprint(fgcc, "(__typeof__(a->r)) ")

この行は、__typeof__(a->r) を直接Cのキャストとして出力します。ここで a->r は、Cgoが生成するCコード内で、Goの関数呼び出しの戻り値を格納するために使用される変数です。__typeof__ はGCC拡張機能であり、コンパイル時に a->r の実際の型を正確に取得します。これにより、CgoがGoの型情報からCの型を「推測」する際の潜在的な不一致が解消されます。Cコンパイラは a->r の宣言からその正確な型を把握しているため、__typeof__(a->r) を用いたキャストは常に有効な型キャストとなります。

この修正により、Cgoが生成するCコードは、より堅牢で正確な型キャストを行うようになり、GoとCの間の相互運用におけるコンパイルエラーのリスクが低減されました。

misc/cgo/test/issue4857.go に追加されたテストケースは、この問題が実際に発生することを示すためのものです。#cgo CFLAGS: -Werror を使用することで、コンパイラの警告がエラーとして扱われ、以前の不適切なキャストがコンパイルエラーを引き起こすことを確認できます。このテストケースは、修正が正しく機能していることを検証するために使用されます。

関連リンク

参考にした情報源リンク