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

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

このコミットは、Go言語のencoding/asn1パッケージにおける整数型の扱いに関する重要な変更を導入しています。具体的には、コードがint型を32ビットと仮定していた問題を修正し、64ビット整数への対応を準備するものです。これにより、異なるアーキテクチャや将来のGoのバージョンにおけるint型のサイズ変更に対して、より堅牢なコードベースが構築されます。

コミット

commit 2f0661558883e60e148d319d89401d56870a9756
Author: Russ Cox <rsc@golang.org>
Date:   Mon Sep 24 10:30:37 2012 -0400

    encoding/asn1: prepare for 64-bit ints
    
    The code was assuming that int = 32 bits. Don't.
    
    Update #2188.
    
    R=agl
    CC=golang-dev
    https://golang.org/cl/6543063

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

https://github.com/golang/go/commit/2f0661558883e60e148d319d89401d56870a9756

元コミット内容

encoding/asn1: prepare for 64-bit ints The code was assuming that int = 32 bits. Don't. Update #2188.

変更の背景

この変更の背景には、Go言語におけるint型のサイズに関する設計上の考慮事項があります。Go言語の仕様では、int型は少なくとも32ビットの符号付き整数であることが保証されていますが、その正確なサイズはコンパイラと実行環境に依存します。多くのシステムでは32ビットですが、64ビットシステムでは64ビットになる可能性があります。

以前のencoding/asn1パッケージのコードは、int型が常に32ビットであるという暗黙の仮定に基づいていました。この仮定は、特にparseInt関数がint64からintへのキャストを行う際に、値が32ビットの範囲に収まることを期待している箇所で問題を引き起こす可能性がありました。もしintが64ビットシステムで64ビットとして扱われるようになると、この仮定は破綻し、予期せぬ挙動やバグにつながる恐れがありました。

コミットメッセージにあるUpdate #2188は、GoのIssue 2188に関連しています。このIssueは、GoのAPIにおける負のインデックスの使用やint型のサイズに関する議論を含んでおり、intが少なくとも32ビットであり、x86_64システムではint64になる可能性についても触れられています。このコミットは、このような将来的なint型のサイズ変更に備え、コードの堅牢性を高めることを目的としています。

前提知識の解説

ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造を記述するための標準的な記法であり、通信プロトコルやデータストレージにおいて、異なるシステム間でデータを交換する際に使用されます。ASN.1は、データの型(整数、文字列、シーケンスなど)と構造を定義し、その定義に基づいてデータをエンコード(符号化)およびデコード(復号化)します。

Go言語のint

Go言語のint型は、プラットフォームに依存する符号付き整数型です。そのサイズは、実行されているシステムのワードサイズ(通常は32ビットまたは64ビット)に合わせられます。これにより、Goプログラムは異なるアーキテクチャで効率的に動作できますが、開発者はintの正確なサイズに依存しないようにコードを記述する必要があります。

reflectパッケージ

Go言語のreflectパッケージは、実行時にプログラムの構造を検査および変更するための機能を提供します。これにより、型情報、フィールド、メソッドなどを動的に操作できます。このコミットでは、reflect.Intreflect.Int32reflect.Int64といった定数を使用して、リフレクションを通じて取得した値の型を識別しています。

技術的詳細

このコミットの主要な技術的変更点は、encoding/asn1パッケージ内の整数解析ロジックの修正です。

  1. parseIntからparseInt32へのリネームと型変更:

    • 元のparseInt関数は、intを返していました。これは、intが32ビットであるという仮定に基づいていました。
    • 変更後、この関数はparseInt32とリネームされ、戻り値の型が明示的にint32に変更されました。これにより、この関数が常に32ビットの整数を扱うことが明確になります。
    • 内部ではparseInt64の結果をint32にキャストし、元の値とキャスト後の値が異なる場合にエラーを返すことで、オーバーフローを検出しています。
  2. parseField関数における整数型のハンドリングの改善:

    • parseField関数は、ASN.1データをGoの構造体のフィールドにマッピングする役割を担っています。
    • 以前はreflect.Intreflect.Int32を個別に処理し、parseIntを使用していました。reflect.Int64parseInt64を使用していました。
    • 変更後、reflect.Int, reflect.Int32, reflect.Int64の3つの型をまとめて処理するようになりました。
    • val.Type().Size() == 4という条件を追加し、Goのint型が32ビット(4バイト)である場合にparseInt32を使用し、それ以外の場合(つまり64ビットの場合)はparseInt64を使用するように分岐しています。これにより、int型の実際のサイズに応じて適切な解析関数が選択されるようになります。

これらの変更により、encoding/asn1パッケージは、int型が32ビットであるか64ビットであるかにかかわらず、正確に整数値を解析できるようになり、将来的なGoのバージョンや異なるアーキテクチャへの対応が強化されました。

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

src/pkg/encoding/asn1/asn1.go

--- a/src/pkg/encoding/asn1/asn1.go
+++ b/src/pkg/encoding/asn1/asn1.go
@@ -77,15 +77,15 @@ func parseInt64(bytes []byte) (ret int64, err error) {
 
 // parseInt treats the given bytes as a big-endian, signed integer and returns
 // the result.
-func parseInt(bytes []byte) (int, error) {
+func parseInt32(bytes []byte) (int32, error) {
 	ret64, err := parseInt64(bytes)
 	if err != nil {
 		return 0, err
 	}
-\tif ret64 != int64(int(ret64)) {\n+\tif ret64 != int64(int32(ret64)) {\n \t\treturn 0, StructuralError{\"integer too large\"}\n \t}\n-\treturn int(ret64), nil
+\treturn int32(ret64), nil
 }
 
 var bigOne = big.NewInt(1)
@@ -670,7 +670,7 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam
 		err = err1
 		return
 	case enumeratedType:
-\t\tparsedInt, err1 := parseInt(innerBytes)
+\t\tparsedInt, err1 := parseInt32(innerBytes)
 		if err1 == nil {
 			v.SetInt(int64(parsedInt))
 		}
@@ -692,19 +692,20 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam
 		err = err1
 		return
-\tcase reflect.Int, reflect.Int32:
-\t\tparsedInt, err1 := parseInt(innerBytes)
-\t\tif err1 == nil {\n-\t\t\tval.SetInt(int64(parsedInt))\n-\t\t}\n-\t\terr = err1
-\t\treturn
-\tcase reflect.Int64:
-\t\tparsedInt, err1 := parseInt64(innerBytes)
-\t\tif err1 == nil {\n-\t\t\tval.SetInt(parsedInt)\n+\tcase reflect.Int, reflect.Int32, reflect.Int64:
+\t\tif val.Type().Size() == 4 {\n+\t\t\tparsedInt, err1 := parseInt32(innerBytes)\n+\t\t\tif err1 == nil {\n+\t\t\t\tval.SetInt(int64(parsedInt))\n+\t\t\t}\n+\t\t\terr = err1
+\t\t} else {\n+\t\t\tparsedInt, err1 := parseInt64(innerBytes)\n+\t\t\tif err1 == nil {\n+\t\t\t\tval.SetInt(parsedInt)\n+\t\t\t}\n+\t\t\terr = err1
 		}
-\t\terr = err1
 		return
 	// TODO(dfc) Add support for the remaining integer types
 	case reflect.Struct:

src/pkg/encoding/asn1/asn1_test.go

--- a/src/pkg/encoding/asn1/asn1_test.go
+++ b/src/pkg/encoding/asn1/asn1_test.go
@@ -64,7 +64,7 @@ var int32TestData = []int32Test{\n 
 func TestParseInt32(t *testing.T) {
 	for i, test := range int32TestData {
-\t\tret, err := parseInt(test.in)
+\t\tret, err := parseInt32(test.in)
 		if (err == nil) != test.ok {
 			t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok)
 		}

コアとなるコードの解説

parseIntからparseInt32への変更

  • : func parseInt(bytes []byte) (int, error)

    • この関数は、バイトスライスを解析してint型の値を返していました。Goのint型はプラットフォーム依存であるため、32ビットシステムでは32ビット、64ビットシステムでは64ビットになります。しかし、この関数は暗黙的に32ビットを想定している可能性がありました。
    • ret64 != int64(int(ret64))というチェックは、int64の値をintにキャストした際に情報が失われないか(つまり、intの範囲に収まるか)を確認していました。もしintが32ビットで、ret64が32ビットの範囲を超える値だった場合、このチェックは正しく機能しますが、intが64ビットだった場合は常にtrueとなり、チェックの意味がなくなります。
  • : func parseInt32(bytes []byte) (int32, error)

    • 関数名がparseInt32に変更され、戻り値の型が明示的にint32になりました。これにより、この関数が常に32ビットの整数を扱うことが明確になります。
    • ret64 != int64(int32(ret64))というチェックに変わりました。これにより、int64の値を明示的にint32にキャストし、その際に情報が失われないかを確認しています。これは、intの実際のサイズに関わらず、常に32ビットの範囲チェックを行うことを保証します。

parseField関数における整数型の処理

  • :

    case reflect.Int, reflect.Int32:
        parsedInt, err1 := parseInt(innerBytes)
        // ...
    case reflect.Int64:
        parsedInt, err1 := parseInt64(innerBytes)
        // ...
    
    • reflect.Intreflect.Int32parseIntで処理され、reflect.Int64parseInt64で処理されていました。ここでもreflect.Intが32ビットであるという仮定が見られます。
  • :

    case reflect.Int, reflect.Int32, reflect.Int64:
        if val.Type().Size() == 4 { // 32-bit int
            parsedInt, err1 := parseInt32(innerBytes)
            // ...
        } else { // 64-bit int
            parsedInt, err1 := parseInt64(innerBytes)
            // ...
        }
    
    • reflect.Int, reflect.Int32, reflect.Int64の3つの型をまとめて処理するようになりました。
    • val.Type().Size() == 4という条件が追加されました。これは、リフレクションで取得した値の型が占めるバイトサイズが4バイト(32ビット)であるかどうかをチェックしています。
      • もし4バイトであれば、それは32ビットの整数型(intが32ビットの場合、またはint32)であると判断し、新しく定義されたparseInt32関数を使用して解析します。
      • それ以外の場合(つまり、intが64ビットの場合、またはint64)は、parseInt64関数を使用して解析します。
    • この変更により、encoding/asn1パッケージは、Goのint型が実行環境で32ビットとして扱われるか64ビットとして扱われるかに応じて、適切な整数解析ロジックを動的に選択できるようになりました。これにより、コードの移植性と堅牢性が大幅に向上しています。

asn1_test.goの変更

  • TestParseInt32関数内で、parseIntの呼び出しがparseInt32に変更されました。これは、関数名変更に伴うテストコードの更新です。

これらの変更は、Go言語のint型のプラットフォーム依存性に対応し、encoding/asn1パッケージがより広範な環境で正しく機能するようにするための重要なステップです。

関連リンク

参考にした情報源リンク