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

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

このコミットは、Goコンパイラのフロントエンドである cmd/gc において、関数呼び出しの引数に関する型エラーメッセージの具体性を向上させるものです。これにより、開発者はより分かりやすいエラーメッセージを受け取ることができ、デバッグの効率が向上します。

コミット

  • コミットハッシュ: a599b4890a06486b1f888216ae87b4cbf3df418a
  • 作者: Jan Ziak
  • コミット日時: 2014年4月11日 15:57:30 +0200
  • コミットメッセージ:
    cmd/gc: increase specificity of errors in function call context
    
    Fixes #7129
    
    LGTM=rsc
    R=rsc
    CC=golang-codereviews
    https://golang.org/cl/86470044
    

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

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

元コミット内容

cmd/gc: increase specificity of errors in function call context

Fixes #7129

LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/86470044

変更の背景

Goコンパイラは、関数呼び出しの際に引数の型が期待される型と一致しない場合、型エラーを報告します。しかし、このエラーメッセージが「function argument」(関数引数)のように汎用的すぎることが問題でした。特に、複数の引数を持つ関数や、多値返却を行う関数を引数として渡すような複雑なケースでは、どの関数のどの引数で型エラーが発生しているのかが不明確であり、開発者が問題の原因を特定するのに時間がかかるという課題がありました。

このコミットは、Go issue #7129 で報告されたこの問題に対応するものです。開発者がより迅速に問題を特定できるよう、エラーメッセージに「argument to f」(fへの引数)のように、エラーが発生した引数がどの関数に属しているかを示す情報を追加することで、エラーメッセージの具体性を高めることを目的としています。

前提知識の解説

Goコンパイラ (cmd/gc)

Go言語の公式コンパイラは、主に cmd/compile パッケージに含まれています。その中でも cmd/gc は、Go言語のソースコードを解析し、中間表現(AST: Abstract Syntax Tree)を生成し、型チェックや最適化を行うフロントエンドの役割を担っていました(Go 1.5以降はコンパイラが統合され、cmd/compile がその役割を担っていますが、このコミットが作成された時点では cmd/gc が主要なコンポーネントでした)。

型チェック (Type Checking)

型チェックは、プログラムが型システムの一貫性ルールに従っているかを検証するプロセスです。コンパイラは、変数、関数引数、戻り値などの型が正しく使用されているかを確認します。型チェックは、コンパイル時に多くのエラーを捕捉し、実行時エラーを防ぐ上で非常に重要です。

typecheckaste 関数

typecheckaste は、Goコンパイラの型チェックフェーズで使用される内部関数の一つです。この関数は、特定のASTノード(ここでは関数呼び出しの引数)の型が、期待される型と一致するかどうかを検証します。型が一致しない場合、エラーメッセージを生成して報告します。この関数の引数には、エラーメッセージの「理由」を示す文字列(why)が含まれており、これが最終的なエラーメッセージの一部となります。

snprintf (C言語の関数)

snprintf はC言語の標準ライブラリ関数で、書式設定された文字列を指定されたバッファに書き込みます。バッファのサイズ制限を考慮するため、バッファオーバーフローを防ぐのに役立ちます。このコミットでは、エラーメッセージを動的に生成するために使用されています。

技術的詳細

このコミットの主要な変更は、src/cmd/gc/typecheck.c ファイル内の typecheck1 関数、特に OCALL(関数呼び出し)のケースにおけるエラーメッセージの生成ロジックにあります。

以前は、関数呼び出しの引数に関する型エラーが発生した場合、typecheckaste 関数に渡される最後の引数(エラーの理由を示す文字列)は常に "function argument" という固定文字列でした。

変更後、この固定文字列の代わりに、より具体的なエラーメッセージを動的に生成するロジックが導入されました。

  1. 新しい変数の導入: char *why; の行が char *why, *desc, descbuf[64]; に変更されました。

    • desc: エラーメッセージの具体的な部分を指すポインタ。
    • descbuf[64]: 具体的なエラーメッセージを格納するための一時的なバッファ。最大64バイトの文字列を格納できます。
  2. 動的なメッセージ生成: typecheckaste の呼び出しの直前に、以下のコードが追加されました。

    if(snprint(descbuf, sizeof descbuf, "argument to %N", n->left) < sizeof descbuf)
        desc = descbuf;
    else
        desc = "function argument";
    
    • snprint(descbuf, sizeof descbuf, "argument to %N", n->left):
      • snprint 関数を使用して、descbuf バッファに書式設定された文字列を書き込みます。
      • sizeof descbuf は、バッファの最大サイズ(64バイト)を指定し、バッファオーバーフローを防ぎます。
      • "argument to %N" は書式文字列です。ここで %N は、n->left(関数呼び出しの対象となる関数ノード)の名前で置き換えられます。例えば、f(g())fh(true, true)h がこれに該当します。
      • この関数は、書き込まれた文字数(終端のNULL文字を除く)を返します。
    • if(...) < sizeof descbuf):
      • snprint がバッファサイズ内に収まる文字列を書き込めた場合(つまり、返された文字数がバッファサイズより小さい場合)、desc ポインタは descbuf を指すように設定されます。これにより、動的に生成された具体的なメッセージが使用されます。
    • else desc = "function argument";:
      • もし snprint が生成しようとした文字列が descbuf のサイズ(64バイト)を超えてしまう場合、desc ポインタは以前の汎用的なメッセージである "function argument" を指すようにフォールバックされます。これは、非常に長い関数名などによってバッファが不足する可能性を考慮した安全策です。
  3. typecheckaste への引数変更: typecheckaste 関数の最後の引数が、固定文字列 "function argument" から、新しく導入された desc 変数に変更されました。

これらの変更により、コンパイラは型エラーを報告する際に、単に「関数引数」と述べるのではなく、「f への引数」や「h への引数」のように、どの関数への引数で問題が発生したのかを明示するようになりました。

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

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -310,7 +310,7 @@ typecheck1(Node **np, int top)
 	int ok, ntop;
 	Type *t, *tp, *missing, *have, *badtype;
 	Val v;
-	char *why;
+	char *why, *desc, descbuf[64];
 	
 	n = *np;
 
@@ -1139,7 +1139,11 @@ reswitch:
 			}
 			break;
 		}
-		typecheckaste(OCALL, n->left, n->isddd, getinargx(t), n->list, "function argument");
+		if(snprint(descbuf, sizeof descbuf, "argument to %N", n->left) < sizeof descbuf)
+			desc = descbuf;
+		else
+			desc = "function argument";
+		typecheckaste(OCALL, n->left, n->isddd, getinargx(t), n->list, desc);
 		ok |= Etop;
 		if(t->outtuple == 0)
 			goto ret;

コアとなるコードの解説

  • char *why; から char *why, *desc, descbuf[64]; への変更:

    • これは、エラーメッセージの具体的な部分を保持するための新しいポインタ desc と、その文字列を一時的に格納するための固定サイズのバッファ descbuf を宣言しています。why は既存の変数で、エラーの一般的な理由を指します。
  • typecheckaste(...) の変更前:

    • typecheckaste(OCALL, n->left, n->isddd, getinargx(t), n->list, "function argument");
    • この行では、関数呼び出しの引数(OCALL)の型チェックを行っています。最後の引数である "function argument" は、型エラーが発生した場合に表示されるメッセージの一部として、引数が「関数引数」であることを示していました。しかし、どの関数の引数であるかは示されていませんでした。
  • if(snprint(descbuf, sizeof descbuf, "argument to %N", n->left) < sizeof descbuf):

    • この条件文は、より具体的なエラーメッセージを生成しようと試みています。
    • snprint は、"argument to %N" というフォーマット文字列と、関数呼び出しの対象となるノード n->left を使って、descbuf に文字列を書き込みます。%N はコンパイラ内部でノードの名前(関数名)に展開されます。
    • 例えば、f(g()) の場合、n->leftf を指し、descbuf には "argument to f" のような文字列が書き込まれます。
    • snprint の戻り値が sizeof descbuf(バッファの最大サイズ)よりも小さい場合、文字列がバッファに収まったことを意味します。
  • desc = descbuf;:

    • 文字列がバッファに収まった場合、desc ポインタは descbuf を指すように設定されます。これにより、動的に生成された具体的なメッセージが使用されます。
  • else desc = "function argument";:

    • もし snprint が生成しようとした文字列が descbuf のサイズを超えてしまった場合(例えば、関数名が非常に長い場合)、desc ポインタは以前の汎用的なメッセージである "function argument" を指すようにフォールバックされます。これは、バッファオーバーフローを防ぎつつ、常に何らかのエラーメッセージを提供するための安全策です。
  • typecheckaste(OCALL, n->left, n->isddd, getinargx(t), n->list, desc);:

    • 最後に、typecheckaste 関数が呼び出されますが、最後の引数には、動的に生成された(またはフォールバックされた)より具体的なメッセージを指す desc が渡されます。

この変更により、Goコンパイラは、型エラーが発生した際に、どの関数の引数で問題が発生したのかを明示する、より具体的で役立つエラーメッセージを出力できるようになりました。これは、開発者がコードのバグを特定し、修正する時間を大幅に短縮するのに貢献します。

関連リンク

  • Go Code Review (Gerrit) リンク: https://golang.org/cl/86470044
  • Go Issue #7129: このコミットが修正した問題の詳細は、Goの公式イシュートラッカーで #7129 として追跡されていました。

参考にした情報源リンク