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

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

このコミットは、Go言語の標準ライブラリであるstrconvパッケージ内のCanBackquote関数の挙動を修正するものです。具体的には、ASCII制御文字の一つであるDEL(Delete、\x7Fまたは\u007F)がバッククォート文字列内で使用できない文字として正しく認識されるように変更が加えられました。これにより、バッククォート文字列の仕様と整合性が保たれ、予期せぬ挙動を防ぐことが目的です。

コミット

commit f34251a91c2d075def51b763c52a0c602f3e09c9
Author: Rob Pike <r@golang.org>
Date:   Wed Mar 19 10:16:48 2014 +1100

    strconv: CanBackquote should reject \x7F
    It's a control character.
    Fixes #7565.
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/77300043

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

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

元コミット内容

strconv: CanBackquote should reject \x7F It's a control character. Fixes #7565.

変更の背景

この変更は、Go言語のIssue #7565「strconv.CanBackquote should reject \x7F」に対応するものです。Go言語におけるバッククォート文字列(raw string literal)は、エスケープシーケンスを解釈せず、改行を含む任意の文字をそのまま表現できる便利な機能です。しかし、その仕様上、一部の制御文字や特定の文字(バッククォート自身など)は含めることができません。

strconv.CanBackquote関数は、与えられた文字列がバッククォート文字列として表現可能かどうかを判定するために使用されます。従来のこの関数は、ASCIIのスペース( )とタブ(\t)以外の制御文字、およびバッククォート文字(`)を検出した場合にfalseを返していました。しかし、ASCII制御文字の範囲には\x7F(DEL)も含まれるにもかかわらず、この文字が明示的にチェックされていませんでした。

DELは、テレタイプ端末などで文字を削除するために使われた歴史的な制御文字であり、現代のテキスト処理においても非表示文字や特殊な意味を持つ文字として扱われることが多いです。バッククォート文字列の「raw」な性質を維持しつつも、このような特殊な制御文字が意図せず含まれることで、文字列の解釈や表示に問題が生じる可能性がありました。このコミットは、この見落としを修正し、DEL文字もバッククォート文字列に含めるべきではないという仕様に合致させることを目的としています。

前提知識の解説

Go言語の文字列リテラル

Go言語には主に2種類の文字列リテラルがあります。

  1. 解釈済み文字列リテラル (Interpreted String Literals): ダブルクォート(")で囲まれた文字列です。バックスラッシュ(\)を用いたエスケープシーケンス(例: \n\t\"\xNN\uNNNN\UNNNNNNNN)が解釈されます。 例: "Hello\nWorld"

  2. 生文字列リテラル (Raw String Literals): バッククォート(`)で囲まれた文字列です。エスケープシーケンスは一切解釈されず、囲まれた文字がそのまま文字列の値となります。改行もそのまま含めることができます。 例: `Hello\nWorld` は、Hello、改行、Worldという文字がそのまま含まれる文字列になります。 ただし、生文字列リテラル自体を閉じるバッククォート文字(`)は、その性質上、生文字列リテラル内に直接含めることはできません。また、Go言語の仕様では、生文字列リテラルには「グラフィック文字」(表示可能な文字)のみを含めるべきであり、制御文字は含めるべきではないとされています。

ASCII制御文字

ASCII(American Standard Code for Information Interchange)は、コンピュータでテキストを表現するための文字コードです。ASCIIコードは0から127までの128個の文字を定義しており、そのうち0から31、および127のコードポイントは「制御文字」(Control Characters)に分類されます。これらは通常、画面に表示される文字ではなく、プリンターの制御、通信プロトコル、テキストのフォーマットなどに使われる特殊な機能を持つ文字です。

  • \x00 (NUL): Null character
  • \x00 - \x1F: 多くの非表示制御文字(例: \n (LF), \t (HT), \r (CR) など)
  • \x20 (SP): Space character
  • \x7F (DEL): Delete character

DEL(Delete)はASCIIコード127(16進数で7F)に割り当てられています。これは、紙テープの文字を消去するために使われたり、端末でカーソル下の文字を削除するキーに割り当てられたりする歴史的な背景を持つ制御文字です。現代のテキスト処理においても、この文字は通常、表示可能な文字とは区別され、特殊な処理を必要とします。

strconvパッケージ

strconvパッケージは、Go言語の標準ライブラリの一部であり、基本的なデータ型(数値、真偽値など)と文字列との間の変換機能を提供します。また、文字列のクォート(引用符付け)やアンクォート(引用符外し)に関するユーティリティ関数も含まれています。

  • strconv.Quote(s string) string: 文字列sをGoの文字列リテラル形式(ダブルクォートで囲み、必要に応じてエスケープ)に変換します。
  • strconv.CanBackquote(s string) bool: 文字列sが生文字列リテラル(バッククォートで囲まれた形式)として表現可能かどうかを判定します。

技術的詳細

このコミットの技術的な核心は、strconv.CanBackquote関数が\x7F(DEL)文字を適切に処理するように修正された点にあります。

CanBackquote関数は、入力文字列sを走査し、生文字列リテラルとして表現できない文字が含まれていないかをチェックします。Go言語の生文字列リテラルの仕様では、バッククォート文字(`)自体と、スペース( )およびタブ(\t)を除くすべての制御文字は許可されていません。

修正前のコードでは、以下の条件でfalseを返していました。 if (s[i] < ' ' && s[i] != '\t') || s[i] == '' これは、「ASCIIコードがスペース(' '\x20)より小さい文字(つまり、\x00から\x1Fまでの制御文字)で、かつタブ(\t)ではない文字」または「バッククォート文字(`` ``)」を検出した場合にfalseを返す、というロジックでした。

しかし、この条件では\x7F(DEL)が考慮されていませんでした。\x7F' '\x20)より小さくないため、最初の条件s[i] < ' 'には合致しません。したがって、\x7FCanBackquote関数によって「バッククォート文字列に含めることができる文字」と誤って判断されていました。

今回の修正では、この条件に|| c == '\u007F'が追加されました。 if (c < ' ' && c != '\t') || c == '' || c == '\u007F' これにより、\u007F(Unicodeエスケープシーケンスで表現されたDEL文字)が明示的にチェックされ、この文字が含まれる場合はCanBackquotefalse`を返すようになりました。これは、Go言語の生文字列リテラルの仕様に完全に準拠するための重要な修正です。

また、テストケースsrc/pkg/strconv/quote_test.go{string(0x7F), false},が追加されたことで、この修正が正しく機能するかどうかが検証されるようになりました。string(0x7F)は、バイト値0x7Fを持つ単一の文字からなる文字列を生成します。このテストケースがfalseを返すことを期待することで、DEL文字が正しく拒否されることを確認しています。

この変更は、strconvパッケージの堅牢性を高め、Go言語の文字列処理における一貫性と正確性を向上させるものです。

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

変更は主にsrc/pkg/strconv/quote.goCanBackquote関数と、それに対応するテストファイルsrc/pkg/strconv/quote_test.goにあります。

src/pkg/strconv/quote.go

--- a/src/pkg/strconv/quote.go
+++ b/src/pkg/strconv/quote.go
@@ -144,7 +144,8 @@ func AppendQuoteRuneToASCII(dst []byte, r rune) []byte {
 // characters other than space and tab.
 func CanBackquote(s string) bool {
 	for i := 0; i < len(s); i++ {
-		if (s[i] < ' ' && s[i] != '\t') || s[i] == '`' {
+		c := s[i]
+		if (c < ' ' && c != '\t') || c == '`' || c == '\u007F' {
 			return false
 		}
 	}

src/pkg/strconv/quote_test.go

--- a/src/pkg/strconv/quote_test.go
+++ b/src/pkg/strconv/quote_test.go
@@ -140,6 +140,7 @@ var canbackquotetests = []canBackquoteTest{
 	{string(29), false},
 	{string(30), false},
 	{string(31), false},
+	{string(0x7F), false},
 	{`' !"$%&'()*+,-./:;<=>?@[\]^_{|}~`, true},
 	{`0123456789`, true},
 	{`ABCDEFGHIJKLMNOPQRSTUVWXYZ`, true},

コアとなるコードの解説

src/pkg/strconv/quote.go の変更

CanBackquote関数内のループで、文字列sの各バイトs[i]cという変数に代入しています。 変更前の条件式: (s[i] < ' ' && s[i] != '\t') || s[i] == ''変更後の条件式:(c < ' ' && c != '\t') || c == '' || c == '\u007F'

追加された|| c == '\u007F'の部分がこのコミットの核心です。

  • c < ' ': ASCIIコードがスペース(\x20)より小さい文字、つまり\x00から\x1Fまでの制御文字を検出します。
  • c != '\t': ただし、タブ文字(\t)は許可されているため、この条件から除外します。
  • c == ''`: バッククォート文字自体は生文字列リテラル内に含められないため、これを検出します。
  • c == '\u007F': 新たに追加された条件で、DEL文字(ASCII 127)を検出します。\u007FはUnicodeエスケープシーケンスでDEL文字を表しています。

これらのいずれかの条件が真となった場合、その文字は生文字列リテラルとして表現できないため、関数は即座にfalseを返します。ループが最後まで実行され、どの禁止文字も検出されなかった場合にのみtrueが返されます。

src/pkg/strconv/quote_test.go の変更

canbackquotetestsというテストケースのスライスに、新しいエントリが追加されました。 {string(0x7F), false},

  • string(0x7F): これは、バイト値0x7F(10進数で127、つまりDEL文字)を持つ単一の文字からなる文字列を作成します。
  • false: この文字列がCanBackquote関数によってfalseと評価されるべきであることを示しています。

このテストケースの追加により、CanBackquote関数がDEL文字を正しく拒否するようになったことが、自動テストによって保証されるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の仕様 - String literals: https://go.dev/ref/spec#String_literals
  • ASCII制御文字に関する情報 (例: Wikipediaなど)
  • Go言語 strconv パッケージのドキュメント: https://pkg.go.dev/strconv
  • Go言語のソースコード: src/pkg/strconv/quote.go および src/pkg/strconv/quote_test.go (コミット時点のバージョン)