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

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

このコミットは、Go言語の標準ライブラリである text/scanner パッケージにおける、不正な16進数および8進数のリテラルに対するエラー報告の改善を目的としたバグ修正です。具体的には、0x の後に続くべき16進数の桁がない場合や、8進数リテラルに不正な数字(8や9)が含まれる場合に、より正確なエラーを報告するようにスキャナーの挙動が修正されています。

コミット

commit ace14d01bda6f8384dea8ab7307e657d80471054
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Aug 15 11:09:34 2012 -0700

    text/scanner: report illegal hexadecimal numbers (bug fix)
    
    R=r
    CC=golang-dev
    https://golang.org/cl/6450136

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

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

元コミット内容

text/scanner: report illegal hexadecimal numbers (bug fix)

このコミットメッセージは、text/scanner パッケージが不正な16進数リテラルを正しく報告するように修正されたバグ修正であることを示しています。

変更の背景

Go言語のコンパイラやツールチェーンにおいて、ソースコードを解析する際には字句解析(Lexical Analysis)というプロセスが不可欠です。このプロセスは、ソースコードの文字列ストリームをトークン(意味のある最小単位)のストリームに変換します。text/scanner パッケージは、このような字句解析の汎用的な機能を提供します。

以前の text/scanner パッケージの実装では、16進数リテラル(0x または 0X で始まる数値)の後に有効な16進数の桁が続かない場合、または8進数リテラル(0 で始まる数値)に8や9といった不正な数字が含まれる場合に、適切なエラーを報告しない、あるいは誤ったトークンとして解釈してしまうバグが存在していました。

例えば、0x のように16進数プレフィックスの後に何も続かない場合や、0xg のように不正な文字が続く場合、あるいは 01238 のように8進数として解釈されるべき数値に 8 が含まれる場合などです。これらのケースは、Go言語の仕様に則った数値リテラルとしては不正であり、コンパイラはエラーを報告する必要があります。

このコミットは、これらの不正な数値リテラルを text/scanner が正しく識別し、適切なエラーメッセージを生成するように修正することで、コンパイラの堅牢性とユーザーへのフィードバックの質を向上させることを目的としています。

前提知識の解説

字句解析 (Lexical Analysis) とスキャナー (Scanner)

字句解析は、コンパイラの最初のフェーズであり、ソースコードをトークンのシーケンスに変換します。この処理を行うプログラムを「字句解析器」または「スキャナー(Scanner)」と呼びます。スキャナーは、空白文字やコメントを無視し、キーワード、識別子、演算子、数値リテラル、文字列リテラルなどを識別します。

Go言語の数値リテラル

Go言語では、整数リテラルは以下の形式で記述できます。

  • 10進数: 123, 0 など。
  • 8進数: 0 で始まり、0 から 7 までの数字が続く。例: 0755, 0123。Go 1.13以降では 0o または 0O プレフィックスも導入されましたが、このコミットが作成された2012年時点では 0 プレフィックスのみでした。
  • 16進数: 0x または 0X で始まり、0 から 9a から fA から F までの数字が続く。例: 0xdeadbeef, 0xFF

Go言語の仕様では、これらのリテラルが正しくない形式で記述された場合(例: 8進数に8や9が含まれる、16進数プレフィックスの後に桁がないなど)は、コンパイルエラーとなるべきです。

text/scanner パッケージ

text/scanner パッケージは、Go言語のソースコードだけでなく、一般的なテキストの字句解析にも使用できる汎用的なスキャナーを提供します。このパッケージは、入力ストリームから文字を読み込み、それをトークンに変換する機能を持っています。Scanner 型は、Scan() メソッドを通じて次のトークンを読み込み、その種類と値を返します。エラーが発生した場合は、Error メソッドを通じて報告されます。

scanNumber 関数

text/scanner パッケージ内の scanNumber 関数は、数値リテラルを解析する責任を負っています。この関数は、現在の文字が数字であると判断された場合に呼び出され、その後の文字を読み進めて、数値リテラルの種類(整数、浮動小数点数)と値を決定します。このコミットの変更は、特に16進数と8進数の解析ロジックに焦点を当てています。

技術的詳細

このコミットの技術的詳細は、text/scanner パッケージの scanNumber 関数における数値リテラルの解析ロジックの改善にあります。

16進数リテラルの解析改善

変更前は、0x または 0X のプレフィックスを検出した後、続く文字が16進数の桁である限り読み進めるループがありました。しかし、このループが一度も実行されなかった場合(つまり、0x の直後に有効な16進数の桁がなかった場合)、スキャナーはエラーを報告せず、不正な状態になる可能性がありました。

この修正では、hasMantissa という新しいブール変数が導入されました。

  1. ch == 'x' || ch == 'X' の条件で16進数と判断された後、hasMantissafalse で初期化します。
  2. 16進数の桁を読み進める for digitVal(ch) < 16 ループ内で、実際に桁が読み込まれた場合に hasMantissa = true と設定します。
  3. ループ終了後、!hasMantissa であれば、つまり 0x の後に有効な16進数の桁が一つもなかった場合に、s.error("illegal hexadecimal number") を呼び出してエラーを報告します。

これにより、0x0xg のような不正な16進数リテラルが正しく「不正な16進数」として扱われるようになりました。

8進数リテラルの解析改善

変更前は、8進数リテラル(0 で始まる数値)の解析において、seenDecimalDigit という変数が使用されていました。これは、0 から 7 以外の数字(つまり 89)が検出されたかどうかを追跡するためのものでした。しかし、変数名が seenDecimalDigit となっており、89 が検出されたことを示すにはやや曖昧でした。

この修正では、seenDecimalDigithas8or9 にリネームしました。

  1. for isDecimal(ch) ループ内で、ch > '7' の条件(つまり 8 または 9 が検出された場合)で has8or9 = true と設定します。
  2. 数値の解析が完了し、それが8進数として扱われるべきであると判断された後(浮動小数点数ではない場合)、if has8or9 の条件で、8 または 9 が含まれていた場合に s.error("illegal octal number") を呼び出してエラーを報告します。

このリネームとロジックの明確化により、8進数リテラルに不正な数字が含まれるケース(例: 01238)がより直感的に検出され、適切なエラーメッセージが生成されるようになりました。

これらの変更は、text/scanner がGo言語の数値リテラル仕様に厳密に準拠し、より堅牢な字句解析を提供するために不可欠です。

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

変更は src/pkg/text/scanner/scanner.gosrc/pkg/text/scanner/scanner_test.go の2ファイルにわたります。

src/pkg/text/scanner/scanner.go

--- a/src/pkg/text/scanner/scanner.go
+++ b/src/pkg/text/scanner/scanner.go
@@ -389,15 +389,20 @@ func (s *Scanner) scanNumber(ch rune) (rune, rune) {
 		if ch == 'x' || ch == 'X' {
 			// hexadecimal int
 			ch = s.next()
+			hasMantissa := false
 			for digitVal(ch) < 16 {
 				ch = s.next()
+				hasMantissa = true
+			}
+			if !hasMantissa {
+				s.error("illegal hexadecimal number")
 			}
 		} else {
 			// octal int or float
-			seenDecimalDigit := false
+			has8or9 := false
 			for isDecimal(ch) {
 				if ch > '7' {
-					seenDecimalDigit = true
+					has8or9 = true
 				}
 				ch = s.next()
 			}
@@ -408,7 +413,7 @@ func (s *Scanner) scanNumber(ch rune) (rune, rune) {
 				return Float, ch
 			}
 			// octal int
-			if seenDecimalDigit {
+			if has8or9 {
 				s.error("illegal octal number")
 			}
 		}

src/pkg/text/scanner/scanner_test.go

--- a/src/pkg/text/scanner/scanner_test.go
+++ b/src/pkg/text/scanner/scanner_test.go
@@ -446,6 +446,9 @@ func TestError(t *testing.T) {
 	testError(t, `"\`"`, "1:3", "illegal char escape", String)
 
 	testError(t, `01238`, "1:6", "illegal octal number", Int)
+	testError(t, `01238123`, "1:9", "illegal octal number", Int)
+	testError(t, `0x`, "1:3", "illegal hexadecimal number", Int)
+	testError(t, `0xg`, "1:3", "illegal hexadecimal number", Int)
 	testError(t, `'aa'`, "1:4", "illegal char literal", Char)
 
 	testError(t, `'`, "1:2", "literal not terminated", Char)

コアとなるコードの解説

src/pkg/text/scanner/scanner.go の変更点

  1. 16進数解析ロジックの追加:

    • ch = s.next() の直後に hasMantissa := false が追加されました。これは、16進数の桁が少なくとも1つ読み込まれたかどうかを追跡するためのフラグです。
    • for digitVal(ch) < 16 ループ内で、ch = s.next() の後に hasMantissa = true が追加されました。これにより、有効な16進数の桁が読み込まれるたびにフラグが true に設定されます。
    • ループの直後に if !hasMantissa { s.error("illegal hexadecimal number") } が追加されました。これは、0x または 0X のプレフィックスの後に有効な16進数の桁が一つも続かなかった場合に、"illegal hexadecimal number" というエラーを報告します。
  2. 8進数解析ロジックの変数名変更と明確化:

    • seenDecimalDigit := falsehas8or9 := false に変更されました。これは、8進数リテラル中に 8 または 9 が含まれているかどうかをより明確に示します。
    • if ch > '7' の条件内で seenDecimalDigit = truehas8or9 = true に変更されました。
    • 最終的な8進数エラーチェック if seenDecimalDigitif has8or9 に変更されました。

これらの変更により、scanNumber 関数は、Go言語の数値リテラル仕様に違反する不正な16進数および8進数リテラルをより正確に識別し、適切なエラーメッセージを生成できるようになりました。

src/pkg/text/scanner/scanner_test.go の変更点

テストファイルには、新しいエラーケースを検証するための3つの testError コールが追加されました。

  • testError(t, 01238123, "1:9", "illegal octal number", Int): 8進数リテラル中に 8 が含まれるケース。
  • testError(t, 0x, "1:3", "illegal hexadecimal number", Int): 16進数プレフィックスの後に何も続かないケース。
  • testError(t, 0xg, "1:3", "illegal hexadecimal number", Int): 16進数プレフィックスの後に不正な文字 g が続くケース。

これらのテストケースの追加は、修正が意図通りに機能し、以前は検出されなかった不正な数値リテラルが正しくエラーとして報告されることを保証します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特に text/scanner パッケージ)
  • Go言語のコミット履歴
  • Go言語のIssueトラッカー (関連するバグ報告がある場合)
  • Go言語のコードレビューシステム (Gerrit) のCL (Change List) ページ: https://golang.org/cl/6450136 (現在は go.dev/cl/6450136 にリダイレクトされます)