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

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

このコミットは、Goコンパイラ(cmd/gc)において、定数シフト演算がオーバーフローした場合にエラーを報告するように修正するものです。これにより、コンパイル時に予期せぬ数値の丸め込みや不正な値の生成を防ぎ、より堅牢なコード生成を保証します。

コミット

commit 79db6ada48d09dbbf47c4fb0f49ebbd2a044a35b
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Thu Feb 16 00:19:42 2012 +0100

    cmd/gc: error on constant shift overflows.
    
    Fixes #3019.
    
    R=golang-dev, rsc
    CC=golang-dev, remy
    https://golang.org/cl/5674044

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

https://github.com/golang/go/commit/79db6ada48d09dbbf47c4fb0f49ebbd2a044a35b

元コミット内容

Goコンパイラにおいて、定数シフト演算がオーバーフローした場合にエラーを発生させるように変更します。これは、Issue #3019を修正するものです。

変更の背景

Go言語では、コンパイル時に定数式を評価します。特に、シフト演算のようなビット操作は、非常に大きな数値を扱う際にオーバーフローを引き起こす可能性があります。従来のコンパイラでは、このような定数シフトのオーバーフローが発生しても、警告やエラーとして報告されず、結果として不正な値が生成されたり、予期せぬ動作につながる可能性がありました。

このコミットは、この問題を解決するために導入されました。定数シフト演算がGoの多倍長整数(Mpint)の表現範囲を超えた場合に、コンパイル時に明示的なエラーを発生させることで、開発者が問題を早期に発見し、修正できるようにします。これにより、コンパイラの堅牢性が向上し、より信頼性の高いGoプログラムの生成に貢献します。

前提知識の解説

Go言語における定数と多倍長整数

Go言語の定数は、その型が明示的に指定されない限り、「型なし定数(untyped constant)」として扱われます。型なし定数は、Goの組み込み型(int, float64など)の範囲に縛られず、任意の精度を持つことができます。これは、コンパイル時に可能な限り正確な計算を行うためのGoの設計思想の一部です。

例えば、const x = 1 << 100 のような式では、x は非常に大きな整数値を保持できます。この「任意の精度」は、Goコンパイラ内部で多倍長整数(Mpint)というデータ構造を用いて実現されています。Mpintは、通常のCPUレジスタのサイズを超える大きな整数値を扱うために使用されるソフトウェア実装の整数型です。

シフト演算とオーバーフロー

シフト演算(<< 左シフト、>> 右シフト)は、数値のビットを左右に移動させる操作です。

  • 左シフト (<<): 数値を2のN乗倍するのと同等です。例えば、x << nx * (2^n) となります。左シフトは、数値が大きくなるため、オーバーフローの可能性が高まります。
  • 右シフト (>>): 数値を2のN乗で割るのと同等です。例えば、x >> nx / (2^n) となります。

定数シフト演算においてオーバーフローが発生するとは、シフト操作の結果が、Goコンパイラが内部的に使用する多倍長整数(Mpint)の表現可能な最大値を超えてしまう状態を指します。Goの型なし定数は任意の精度を持つとはいえ、コンパイラ内部のMpint実装には物理的なメモリや計算リソースの制約があります。この制約を超えた場合に、オーバーフローとして検出される必要があります。

src/cmd/gc/mparith2.cについて

src/cmd/gc/mparith2.cは、Goコンパイラ(cmd/gc)の一部であり、多倍長整数(Mpint)に対する算術演算を実装しているC言語のファイルです。これには、加算、減算、乗算、除算、そしてシフト演算などが含まれます。このファイルは、Go言語の定数計算の正確性を保証する上で非常に重要な役割を担っています。

技術的詳細

このコミットの主要な変更点は、src/cmd/gc/mparith2.c内の多倍長整数シフト関数にオーバーフロー検出ロジックを追加したことです。

具体的には、以下の関数が変更されました。

  1. mplsh(Mpint *a) -> mplsh(Mpint *a, int quiet):

    • この関数は、多倍長整数 a を1ビット左シフトします。
    • 変更前はオーバーフローを無視していましたが、変更後は quiet という新しい引数が追加されました。
    • quiet が0(false)の場合、シフト操作によってオーバーフローが発生すると(a->ovfがセットされる)、yyerror("constant shift overflow") を呼び出してコンパイルエラーを報告するようになりました。
    • a->ovf は、Mpint構造体に追加された新しいフラグで、オーバーフローが発生したかどうかを示します。
  2. mplshw(Mpint *a) -> mplshw(Mpint *a, int quiet):

    • この関数は、多倍長整数 aMpscale ビット(通常は32ビットまたは64ビット)左シフトします。これは、ワード単位のシフトに相当します。
    • mplsh と同様に quiet 引数が追加され、オーバーフロー検出とエラー報告のロジックが組み込まれました。
    • 特に、最上位ワード(a->a[Mpprec-1])に値がある状態でシフトしようとすると、オーバーフローと見なされます。
  3. mpshiftfix(Mpint *a, int s):

    • この関数は、多倍長整数 as ビット左シフト(s >= 0の場合)または右シフト(s < 0の場合)します。
    • 内部で mplsh および mplshw を呼び出す際に、quiet 引数に 0 を渡すように変更されました。これにより、mpshiftfix を通じたシフト操作でオーバーフローが発生した場合に、エラーが報告されるようになります。
  4. mpmulfixfix(Mpint *a, Mpint *b) および mpdivmodfixfix(Mpint *q, Mpint *r, Mpint *n, Mpint *d):

    • これらの関数は、乗算と除算の内部で mplsh を呼び出しますが、これらの呼び出しでは quiet 引数に 1 を渡すように変更されました。これは、これらの内部的なシフト操作ではオーバーフローをエラーとして報告する必要がないためです。例えば、除算の準備段階で一時的に大きな数値を扱う場合など、中間結果のオーバーフローは問題にならないことがあります。
  5. test/const2.go:

    • 新しいテストケースが追加されました。
    • const AlsoLargeA = LargeA << 400 << 400 >> 400 >> 400 のような定数式が追加され、これが「constant shift overflow」エラーを発生させることを期待するようになりました。これは、LargeA を非常に大きな量で左シフトすることで、Mpintの表現範囲を超えさせることを意図しています。

これらの変更により、Goコンパイラは定数シフト演算のオーバーフローを正確に検出し、開発者に適切なエラーメッセージを提供できるようになりました。

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

diff --git a/src/cmd/gc/mparith2.c b/src/cmd/gc/mparith2.c
index c802e4468a..8e52ff2162 100644
--- a/src/cmd/gc/mparith2.c
+++ b/src/cmd/gc/mparith2.c
@@ -27,10 +27,10 @@ mplen(Mpint *a)\n \n //\n // left shift mpint by one\n-// ignores sign and overflow\n+// ignores sign\n //
 static void\n-mplsh(Mpint *a)\n+mplsh(Mpint *a, int quiet)\n {\n \tlong *a1, x;\n \tint i, c;\n@@ -46,19 +46,27 @@ mplsh(Mpint *a)\n \t\t}\n \t\t*a1++ = x;\n \t}\n+\ta->ovf = c;\n+\tif(a->ovf && !quiet)\n+\t\tyyerror(\"constant shift overflow\");\n }\n \n //\n // left shift mpint by Mpscale\n-// ignores sign and overflow\n+// ignores sign\n //
 static void\n-mplshw(Mpint *a)\n+mplshw(Mpint *a, int quiet)\n {\n \tlong *a1;\n \tint i;\n \n \ta1 = &a->a[Mpprec-1];\n+\tif(*a1) {\n+\t\ta->ovf = 1;\n+\t\tif(!quiet)\n+\t\t\tyyerror(\"constant shift overflow\");\n+\t}\n \tfor(i=1; i<Mpprec; i++) {\n \t\ta1[0] = a1[-1];\n \t\ta1--;\n@@ -168,11 +176,11 @@ mpshiftfix(Mpint *a, int s)\n {\n \tif(s >= 0) {\n \t\twhile(s >= Mpscale) {\n-\t\t\tmplshw(a);\n+\t\t\tmplshw(a, 0);\n \t\t\ts -= Mpscale;\n \t\t}\n \t\twhile(s > 0) {\n-\t\t\tmplsh(a);\n+\t\t\tmplsh(a, 0);\n \t\t\ts--;\n \t\t}\n \t} else {\n@@ -294,7 +302,7 @@ mpmulfixfix(Mpint *a, Mpint *b)\n \t\tfor(j=0; j<Mpscale; j++) {\n \t\t\tif(x & 1)\n \t\t\t\tmpaddfixfix(&q, &s, 1);\n-\t\t\tmplsh(&s);\n+\t\t\tmplsh(&s, 1);\n \t\t\tx >>= 1;\n \t\t}\n \t}\n@@ -606,7 +614,7 @@ mpdivmodfixfix(Mpint *q, Mpint *r, Mpint *n, Mpint *d)\n \tfor(i=0; i<Mpprec*Mpscale; i++) {\n \t\tif(mpcmp(d, r) > 0)\n \t\t\tbreak;\n-\t\tmplsh(d);\n+\t\tmplsh(d, 1);\n \t}\n \n \t// if it never happens\n@@ -625,7 +633,7 @@ mpdivmodfixfix(Mpint *q, Mpint *r, Mpint *n, Mpint *d)\n \t// when done the remaining numerator\n \t// will be the remainder\n \tfor(; i>0; i--) {\n-\t\tmplsh(q);\n+\t\tmplsh(q, 1);\n \t\tmprsh(d);\n \t\tif(mpcmp(d, r) <= 0) {\n \t\t\tmpaddcfix(q, 1);\ndiff --git a/test/const2.go b/test/const2.go\nindex b0837354ab..12c5c24af0 100644\n--- a/test/const2.go\n+++ b/test/const2.go\n@@ -13,4 +13,6 @@ const (\n \n const LargeA = 1000000000000000000\n const LargeB = LargeA * LargeA * LargeA\n-const LargeC = LargeB * LargeB * LargeB  // ERROR \"constant multiplication overflow\"\n+const LargeC = LargeB * LargeB * LargeB // ERROR \"constant multiplication overflow\"\n+\n+const AlsoLargeA = LargeA << 400 << 400 >> 400 >> 400 // ERROR \"constant shift overflow\"\n```

## コアとなるコードの解説

### `src/cmd/gc/mparith2.c` の変更点

1.  **`mplsh` 関数の変更**:
    ```c
    // left shift mpint by one
    // ignores sign
    static void
    mplsh(Mpint *a, int quiet) // quiet引数が追加
    {
        // ... 既存のシフトロジック ...
        a->ovf = c; // キャリービットcをオーバーフローフラグa->ovfに設定
        if(a->ovf && !quiet) // オーバーフローがあり、かつquietでない場合
            yyerror("constant shift overflow"); // エラー報告
    }
    ```
    *   `mplsh` 関数に `int quiet` 引数が追加されました。これは、このシフト操作がオーバーフローをエラーとして報告すべきかどうかを制御します。
    *   シフト操作の結果生じたキャリービット `c` が `a->ovf` に格納されます。`a->ovf` は `Mpint` 構造体に追加された新しいフィールドで、オーバーフロー状態を保持します。
    *   `a->ovf` が真(オーバーフローが発生)であり、かつ `quiet` が偽(エラーを報告すべき)である場合に、`yyerror` 関数が呼び出され、「constant shift overflow」というコンパイルエラーが報告されます。

2.  **`mplshw` 関数の変更**:
    ```c
    // left shift mpint by Mpscale
    // ignores sign
    static void
    mplshw(Mpint *a, int quiet) // quiet引数が追加
    {
        long *a1;
        int i;

        a1 = &a->a[Mpprec-1];
        if(*a1) { // 最上位ワードに値がある場合
            a->ovf = 1; // オーバーフローフラグをセット
            if(!quiet) // quietでない場合
                yyerror("constant shift overflow"); // エラー報告
        }
        // ... 既存のシフトロジック ...
    }
    ```
    *   `mplshw` 関数にも `int quiet` 引数が追加されました。
    *   この関数はワード単位でシフトするため、最上位ワード(`a->a[Mpprec-1]`)に非ゼロの値がある状態でシフトしようとすると、それはオーバーフローと見なされます。
    *   その場合、`a->ovf` を1に設定し、`quiet` でない場合はエラーを報告します。

3.  **`mpshiftfix` 関数の変更**:
    ```c
    // ...
    if(s >= 0) {
        while(s >= Mpscale) {
            mplshw(a, 0); // quietを0に設定して呼び出し
            s -= Mpscale;
        }
        while(s > 0) {
            mplsh(a, 0); // quietを0に設定して呼び出し
            s--;
        }
    }
    // ...
    ```
    *   `mpshiftfix` は、Goの定数シフト演算の主要なエントリポイントの一つです。
    *   この関数から `mplsh` および `mplshw` を呼び出す際に、`quiet` 引数に `0` を明示的に渡すようになりました。これにより、`mpshiftfix` を介した定数シフト演算でオーバーフローが発生した場合、コンパイラはエラーを報告するようになります。

4.  **`mpmulfixfix` および `mpdivmodfixfix` 関数の変更**:
    ```c
    // mpmulfixfix内
    // ...
    mplsh(&s, 1); // quietを1に設定して呼び出し
    // ...

    // mpdivmodfixfix内
    // ...
    mplsh(d, 1); // quietを1に設定して呼び出し
    // ...
    mplsh(q, 1); // quietを1に設定して呼び出し
    // ...
    ```
    *   これらの関数は、乗算や除算の内部で多倍長整数をシフトする際に `mplsh` を使用します。
    *   これらの内部的なシフト操作では、中間結果のオーバーフローは通常問題にならないため、`quiet` 引数に `1` を渡すことで、エラー報告を抑制しています。これにより、不必要なエラーメッセージの発生を防ぎ、コンパイラの出力のノイズを減らします。

### `test/const2.go` の変更点

```go
const LargeA = 1000000000000000000
const LargeB = LargeA * LargeA * LargeA
const LargeC = LargeB * LargeB * LargeB // ERROR "constant multiplication overflow"

const AlsoLargeA = LargeA << 400 << 400 >> 400 >> 400 // ERROR "constant shift overflow"
  • const AlsoLargeA という新しい定数定義が追加されました。
  • LargeA を非常に大きな量(400ビット、さらに400ビット)左シフトすることで、多倍長整数の表現範囲を超えさせ、意図的に定数シフトオーバーフローを発生させています。
  • // ERROR "constant shift overflow" というコメントは、この行がコンパイル時に指定されたエラーメッセージを発生させることを期待していることを示しています。これにより、コンパイラの修正が正しく機能しているかどうかが検証されます。

これらの変更により、Goコンパイラは定数シフト演算におけるオーバーフローを適切に検出し、開発者に明確なエラーメッセージを提供することで、より安全で信頼性の高いGoプログラムの作成を支援します。

関連リンク

参考にした情報源リンク