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

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

このコミットは、Goコンパイラの型チェックロジックを修正し、make関数の引数に非整数値が渡された場合に、より正確なエラー診断を行うように変更しています。具体的には、src/cmd/gc/typecheck.cが変更され、make関数の引数チェックが強化されました。また、この修正を検証するための新しいテストケースtest/fixedbugs/issue7223.goが追加されています。

コミット

commit e33e47e844cdce5a5dedfd0c1c72e480f12db6f1
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sun Feb 23 16:31:48 2014 -0500

    cmd/gc: diagnose "make([]T, non-integer)" correctly.
    Fixes #7223.
    
    LGTM=rsc
    R=golang-codereviews, gobot, rsc
    CC=golang-codereviews
    https://golang.org/cl/63040043

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

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

元コミット内容

cmd/gc: diagnose "make([]T, non-integer)" correctly. Fixes #7223.

make([]T, non-integer)のような形式でmake関数が呼び出された際に、コンパイラが正しく診断(エラー報告)するように修正します。これはIssue #7223を修正するものです。

変更の背景

このコミットは、Goコンパイラがmake組み込み関数の引数として非整数値が与えられた場合に、誤ったエラーメッセージを出力したり、あるいは適切なエラーを報告しなかったりする問題を解決するために行われました。具体的には、Go issue #7223で報告された問題に対応しています。

元のコンパイラでは、make([]byte, nil)make([]byte, true)make([]byte, "abc")のように、make関数の長さ(len)や容量(cap)の引数に整数型ではない値が渡された場合、コンパイラが期待通りの「非整数引数」というエラーを報告しないことがありました。特に、nilのような「理想型(ideal type)」のリテラルが渡された場合に、コンパイラの型推論とエラー診断のロジックが不十分であったことが原因です。

この修正の目的は、make関数の引数チェックをより堅牢にし、開発者が意図しない型を引数に渡してしまった場合に、明確で正確なエラーメッセージを提供することです。これにより、Goコードの品質と開発者の生産性が向上します。

前提知識の解説

Go言語のmake関数

Go言語のmake組み込み関数は、スライス、マップ、チャネルといった組み込みの参照型を初期化するために使用されます。

  • スライス: make([]Type, length, capacity)またはmake([]Type, length)の形式で、指定された長さと容量を持つスライスを作成します。lengthcapacityは整数型である必要があります。
  • マップ: make(map[KeyType]ValueType, capacity)の形式で、指定された初期容量を持つマップを作成します。
  • チャネル: make(chan Type, capacity)の形式で、指定されたバッファ容量を持つチャネルを作成します。

このコミットが関連するのは、スライスのmake関数におけるlengthcapacity引数です。これらは非負の整数でなければなりません。

Goコンパイラの型チェック

Goコンパイラは、ソースコードがGo言語の型システム規則に準拠しているかを検証する「型チェック」のフェーズを持っています。このフェーズでは、各変数の型、関数の引数と戻り値の型、演算子のオペランドの型などが検査されます。型チェックは、プログラムが実行される前に多くのエラーを捕捉し、実行時エラーを防ぐ上で非常に重要です。

理想型 (Ideal Type)

Go言語には「理想型(ideal type)」という概念があります。これは、型付けされていない定数(untyped constants)に適用される特殊な型です。例えば、10という数値リテラルは、それ自体では特定の整数型(int, int32, int64など)を持ちません。これは理想的な整数型(TIDEAL)として扱われます。同様に、nilも理想的なポインタ型、スライス型、マップ型、チャネル型、関数型、インターフェース型など、文脈によって様々な型に変換されうる理想型です。

理想型は、具体的な型が決定されるまで、より柔軟な型推論を可能にします。しかし、最終的には具体的な型に変換される必要があります。この変換(defaultlit関数によって行われることが多い)の際に、型チェックのロジックが複雑になることがあります。

src/cmd/gc/typecheck.c

このファイルは、Goコンパイラのフロントエンドの一部であり、主に型チェックとAST(抽象構文木)の変換を担当しています。checkmake関数は、make組み込み関数の引数を検証する役割を担っています。

技術的詳細

このコミットの主要な変更は、src/cmd/gc/typecheck.c内のcheckmake関数にあります。この関数は、make関数の引数(長さや容量)が有効であるかをチェックします。

変更前は、checkmake関数はリテラル(定数)の引数に対して、まずtoint関数で整数に変換しようとし、その後、負の値でないか、またはmaxintval[TINT](Goのint型で表現できる最大値)を超えていないかをチェックしていました。しかし、このロジックは、リテラルが整数型ではない場合(例: nil, true, 文字列リテラル)に不十分でした。

変更後のcheckmake関数では、リテラル引数nの型(n->val.ctype)をswitch文で明示的にチェックするようになりました。

  1. CTINT, CTRUNE, CTFLT, CTCPLXの場合:

    • これらの型は数値リテラルを表します。以前と同様にtointで整数に変換し、負の値でないか、または大きすぎないかをチェックします。
    • 重要な変更点として、defaultlit(&n, types[TINT]);(理想型をint型にデフォルト変換する処理)が、これらの数値リテラルに対する範囲チェックの後に行われるようになりました。これにより、「定数NNNがint型でオーバーフローします」のような冗長なエラーを避けることができます。
  2. その他の場合(default:

    • nilや文字列リテラルなど、数値ではないリテラルがここに含まれます。これらの場合は、breakしてswitch文を抜けます。これにより、これらの非数値リテラルが数値として扱われることを防ぎます。

さらに、リテラルではない引数(変数など)や、switch文で処理されなかったリテラル引数に対するチェックも変更されました。

変更前:

if(!isint[n->type->etype]) {
    yyerror("non-integer %s argument in make(%T) - %T", arg, t, n->type);
    return -1;
}

変更後:

if(!isint[n->type->etype] && n->type->etype != TIDEAL) {
    yyerror("non-integer %s argument in make(%T) - %T", arg, t, n->type);
    return -1;
}

この変更は非常に重要です。以前は、引数の型が整数型でない場合にエラーを報告していました。しかし、nilのような理想型(TIDEAL)は、まだ具体的な整数型に変換されていないため、isint[n->type->etype]falseになります。このため、nilが渡された場合でも、このエラーチェックに引っかかってしまい、non-integerというエラーが報告されていました。

変更後は、n->type->etype != TIDEALという条件が追加されました。これにより、引数がまだ理想型である場合は、このnon-integerエラーをすぐに報告せず、後続のdefaultlitによる型変換に任せるようになりました。defaultlitnilintに変換しようとすると失敗し、より適切なエラー(例: cannot convert nil to type int)が報告されるようになります。

この修正により、make([]byte, nil)のようなコードに対して、以前は「non-integer len argument in make([]byte) - nil」というエラーが出ていたものが、より正確な「cannot convert nil to type int」というエラーに変わるなど、診断メッセージが改善されました。

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

src/cmd/gc/typecheck.ccheckmake関数における変更点です。

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -3231,29 +3231,38 @@ static int
 checkmake(Type *t, char *arg, Node *n)
 {
  if(n->op == OLITERAL) {
-  n->val = toint(n->val);
-  if(mpcmpfixc(n->val.u.xval, 0) < 0) {
-   yyerror("negative %s argument in make(%T)", arg, t);
-   return -1;
-  }
-  if(mpcmpfixfix(n->val.u.xval, maxintval[TINT]) > 0) {
-   yyerror("%s argument too large in make(%T)", arg, t);
-   return -1;
+  switch(n->val.ctype) {
+  case CTINT:
+  case CTRUNE:
+  case CTFLT:
+  case CTCPLX:
+   n->val = toint(n->val);
+   if(mpcmpfixc(n->val.u.xval, 0) < 0) {
+    yyerror("negative %s argument in make(%T)", arg, t);
+    return -1;
+   }
+   if(mpcmpfixfix(n->val.u.xval, maxintval[TINT]) > 0) {
+    yyerror("%s argument too large in make(%T)", arg, t);
+    return -1;
+   }
+   
+   // Delay defaultlit until after we've checked range, to avoid
+   // a redundant "constant NNN overflows int" error.
+   defaultlit(&n, types[TINT]);
+   return 0;
+  default:
+          break;
+  }
+ }
+ 
+ // Defaultlit still necessary for non-constant: n might be 1<<k.
+ defaultlit(&n, types[TINT]);
+
+ if(!isint[n->type->etype] && n->type->etype != TIDEAL) {
+  yyerror("non-integer %s argument in make(%T) - %T", arg, t, n->type);
+  return -1;
+ }
+
+ return 0;
 }
- 
-  // Delay defaultlit until after we've checked range, to avoid
-  // a redundant "constant NNN overflows int" error.
-  defaultlit(&n, types[TINT]);
-  return 0;
- }
- 
- // Defaultlit still necessary for non-constant: n might be 1<<k.
- defaultlit(&n, types[TINT]);
- 
- if(!isint[n->type->etype]) {
-  yyerror("non-integer %s argument in make(%T) - %T", arg, t, n->type);
-  return -1;
- }
- return 0;
-}

test/fixedbugs/issue7223.goの追加:

// errorcheck

// Copyright 2014 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 main

var bits1 uint = 10
const bits2 uint = 10

func main() {
	_ = make([]byte, 1<<bits1)
	_ = make([]byte, 1<<bits2)
	_ = make([]byte, nil) // ERROR "non-integer.*len"
	_ = make([]byte, nil, 2) // ERROR "non-integer.*len"
	_ = make([]byte, 1, nil) // ERROR "non-integer.*cap"
	_ = make([]byte, true) // ERROR "non-integer.*len"
	_ = make([]byte, "abc") // ERROR "non-integer.*len"
}

コアとなるコードの解説

src/cmd/gc/typecheck.ccheckmake関数

  1. リテラル引数の処理の改善:

    • 変更前は、OLITERAL(リテラル)の場合、一律にtoint(n->val)で整数に変換しようとしていました。これは、niltrue、文字列リテラルなど、整数に変換できないリテラルに対して不適切な動作を引き起こす可能性がありました。
    • 変更後は、switch(n->val.ctype)が導入され、リテラルの具体的な型(CTINT, CTRUNE, CTFLT, CTCPLXなど)に基づいて処理を分岐しています。
      • CTINT, CTRUNE, CTFLT, CTCPLX(整数、ルーン、浮動小数点、複素数リテラル)の場合のみ、以前と同様にtointで整数に変換し、範囲チェックを行います。このブロックの最後にdefaultlit(&n, types[TINT]);が移動されたことで、数値リテラルの範囲チェックが先に行われ、その後にint型へのデフォルト変換が行われるため、より正確なエラーメッセージ(例: constant NNN overflows int)が期待通りに表示されるようになります。
      • default:ケースではbreak;が実行され、数値ではないリテラル(例: nil, true, "abc")はswitch文を抜けて、後続の汎用的な型チェックロジックに委ねられます。
  2. 非整数引数チェックの修正:

    • 変更前は、if(!isint[n->type->etype])という条件で、引数の型が整数型でない場合にエラーを報告していました。
    • 変更後は、if(!isint[n->type->etype] && n->type->etype != TIDEAL)という条件に変わりました。
      • n->type->etype != TIDEALという条件が追加されたことで、引数がまだ「理想型」(nilなど)である場合は、このnon-integerエラーをすぐに報告しないようになりました。理想型は、まだ具体的な型に解決されていないため、この段階でnon-integerと決めつけるのは早計です。
      • これにより、make([]byte, nil)のようなケースでは、defaultlit(&n, types[TINT]);nilintに変換しようとして失敗し、より適切な「cannot convert nil to type int」のようなエラーが報告されるようになります。

test/fixedbugs/issue7223.go

このテストファイルは、make関数の引数に様々な非整数値を与えた場合に、コンパイラが期待通りのエラーメッセージを生成するかどうかを検証します。

  • _ = make([]byte, nil): nilを長さ引数に渡すケース。
  • _ = make([]byte, nil, 2): nilを長さ引数に、整数を容量引数に渡すケース。
  • _ = make([]byte, 1, nil): 整数を長さ引数に、nilを容量引数に渡すケース。
  • _ = make([]byte, true): ブール値を長さ引数に渡すケース。
  • _ = make([]byte, "abc"): 文字列を長さ引数に渡すケース。

これらの行には// ERROR "non-integer.*len"// ERROR "non-integer.*cap"といったコメントが付与されており、これはerrorcheckツールが期待するエラーメッセージの正規表現パターンを示しています。このテストがパスすることで、コンパイラがmake関数の引数チェックにおいて、非整数値に対して正しくエラーを報告するようになったことが確認できます。

関連リンク

参考にした情報源リンク

  • Go issue #7223: cmd/gc: make([]T, non-integer) should diagnose correctly. (https://golang.org/issue/7223)
  • Go Code Review 63040043: cmd/gc: diagnose "make([]T, non-integer)" correctly. (https://golang.org/cl/63040043)
  • Go言語のmake関数に関する公式ドキュメントやチュートリアル (一般的な知識のため特定のURLは省略)
  • Goコンパイラの内部構造に関する一般的な情報 (一般的な知識のため特定のURLは省略)
  • Go言語の型システム、特に理想型に関する情報 (一般的な知識のため特定のURLは省略)