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

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

このコミットは、Go言語の初期ランタイムの一部であったlib9ライブラリ内のctime.cファイルにおける、Clang 3.1コンパイラが発する警告を修正するものです。具体的には、文字列リテラルに対するポインタ演算の記述方法を変更することで、コンパイラの警告を解消し、コードの堅牢性を向上させています。

コミット

lib9: fix warning under clang 3.1

Fixes #3534.

R=golang-dev, rsc CC=golang-dev https://golang.org/cl/6035054

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

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

元コミット内容

commit b2c6116843a8881debb003168aacaf7c9d488472
Author: Dave Cheney <dave@cheney.net>
Date:   Wed Apr 18 09:57:00 2012 +1000

    lib9: fix warning under clang 3.1
    
    Fixes #3534.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6035054

変更の背景

この変更は、Go言語のビルドプロセスにおいて、Clang 3.1コンパイラを使用した場合にsrc/lib9/ctime.cファイルで発生していた警告を修正するために行われました。lib9は、Goの初期ランタイムが強く影響を受けたPlan 9オペレーティングシステムのライブラリ群です。

コミットメッセージには「Fixes #3534」とありますが、現在のGoプロジェクトにおけるIssue 3534はMattermostの脆弱性に関するものであり、このコミットとは無関係です。これは、GoプロジェクトのIssueトラッカーの番号が時間とともに再利用されたか、あるいは当時の特定の内部的な追跡番号であった可能性が高いです。このコミットが作成された2012年当時、Clang 3.1は比較的新しいコンパイラであり、その厳格なチェックによって、従来のCコードでは見過ごされがちだった潜在的な問題や、より安全なコーディングスタイルを促す警告が発せられることがありました。

特に、ctimeのようなC標準ライブラリの関数は、その設計上、バッファオーバーフローなどのセキュリティ上の脆弱性を引き起こす可能性があるため、現代のコンパイラでは「安全でない関数」として警告の対象となることがあります。このコミットは、そのような警告の一つに対応し、コードの品質と移植性を向上させることを目的としています。

前提知識の解説

lib9

lib9は、ベル研究所で開発されたオペレーティングシステム「Plan 9 from Bell Labs」に由来するライブラリ群です。Go言語の設計と初期実装は、Plan 9の思想や技術的要素から大きな影響を受けており、Goの初期ランタイムにはlib9の一部が組み込まれていました。これらのライブラリは、Goのクロスプラットフォーム対応やシステムプログラミングの基盤の一部を形成していました。

ctime関数

ctimeは、C標準ライブラリ(time.hヘッダ)で定義されている関数の一つです。time_t型の時間値(通常はUnixエポックからの秒数)を受け取り、それを人間が読める形式の文字列(例: "Wed Jan 02 02:03:55 1980\n")に変換して返します。

しかし、ctime関数は内部的に静的バッファを使用するため、スレッドセーフではなく、またバッファのサイズが固定されているため、潜在的なバッファオーバーフローのリスクがあります。このため、現代のC/C++コンパイラや静的解析ツールでは、ctimeのような関数を「安全でない」とみなし、使用を避けるか、より安全な代替関数(例: strftime)を使用するよう警告を発することが一般的です。

Clang 3.1

Clangは、LLVMプロジェクトの一部として開発されているC、C++、Objective-C、Objective-C++コンパイラのフロントエンドです。Clangは、GCC(GNU Compiler Collection)に代わるものとして設計され、高速なコンパイル、優れたエラー診断、モジュール性などの特徴を持っています。

Clang 3.1は、2012年3月頃にリリースされたバージョンであり、当時の最新のC言語標準(C99、C11)への準拠を進めるとともに、より厳格なコードチェックと警告機能を提供していました。このコミットが修正している警告は、Clang 3.1が導入した、あるいはより厳格になったチェックの一つであると考えられます。

C言語の文字列リテラルとポインタ演算

C言語において、ダブルクォーテーションで囲まれた文字列(例: "Hello")は「文字列リテラル」と呼ばれ、通常は読み取り専用のメモリ領域に格納されます。文字列リテラルはconst char[]型として扱われることが多く、その内容を変更しようとすると未定義動作を引き起こします。

ポインタ演算は、ポインタに整数を加算または減算することで、メモリ上の異なる位置を指す新しいポインタを生成する操作です。例えば、char *p = "ABC"; p + 1 は、文字列"ABC"の2番目の文字'B'を指すポインタを生成します。

このコミットで問題となったのは、文字列リテラルを指すポインタに対して直接整数を加算する形式(例: "文字列" + オフセット)が、一部のコンパイラで警告の対象となる場合がある点です。これは、文字列リテラルがchar *型に暗黙的に変換され、その結果として非constポインタとして扱われる可能性があるためです。コンパイラは、この非constポインタが後で変更される可能性があると解釈し、潜在的な問題として警告を発することがあります。

技術的詳細

このコミットが修正している警告は、C言語の文字列リテラルに対するポインタ演算の記述方法に起因しています。

元のコードでは、曜日名や月名の文字列リテラルから、tm->tm_wday(曜日)やtm->tm_mon(月)の値に基づいて適切な部分文字列の先頭へのポインタを取得するために、以下のような形式を使用していました。

"SunMonTueWedThuFriSat" + (tm->tm_wday * 3)
"JanFebMarAprMayJunJulAugSepOctNovDec" + (tm->tm_mon * 3)

この記述はC言語の標準では有効なポインタ演算であり、文字列リテラルの先頭アドレスにオフセットを加算することで、目的の3文字の略語の先頭を指すポインタを得ています。しかし、Clang 3.1のような一部のコンパイラは、このような形式に対して警告を発することがあります。

警告の理由は、文字列リテラルがchar *型に暗黙的に変換される際に、そのポインタが非constとして扱われる可能性があるためです。コンパイラは、この非constポインタが後で変更される可能性があると解釈し、読み取り専用メモリに格納されている文字列リテラルを変更しようとする潜在的な未定義動作を防ぐために警告を発します。これは、コードの安全性と堅牢性を高めるためのコンパイラのヒューリスティックなチェックの一環です。

修正後のコードでは、この問題を回避するために、配列のインデックス演算子[]とアドレス演算子&を組み合わせています。

&"SunMonTueWedThuFriSat"[tm->tm_wday * 3]
&"JanFebMarAprMayJunJulAugSepOctNovDec"[tm->tm_mon * 3]

この形式は、文字列リテラルをcharの配列として扱い、[tm->tm_wday * 3]というインデックスで特定の文字にアクセスし、その文字のアドレスを&演算子で取得しています。この方法は、文字列リテラルが配列として扱われることをより明示的に示し、結果として得られるポインタがconst char *型として適切に扱われるため、コンパイラが警告を発する可能性が低くなります。機能的には元のコードと同じ結果をもたらしますが、コンパイラにとってより安全で明確な記述と認識されます。

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

--- a/src/lib9/ctime.c
+++ b/src/lib9/ctime.c
@@ -16,8 +16,8 @@ p9ctime(long t)
 	tt = t;
 	tm = localtime(&tt);
 	snprint(buf, sizeof buf, "%3.3s %3.3s %02d %02d:%02d:%02d %3.3s %d\n",
-\t\t"SunMonTueWedThuFriSat"+(tm->tm_wday*3),\
-\t\t"JanFebMarAprMayJunJulAugSepOctNovDec"+(tm->tm_mon*3),\
+\t\t&"SunMonTueWedThuFriSat"[tm->tm_wday*3],\
+\t\t&"JanFebMarAprMayJunJulAugSepOctNovDec"[tm->tm_mon*3],\
 		tm->tm_mday,
 		tm->tm_hour,
 		tm->tm_min,

コアとなるコードの解説

変更はsrc/lib9/ctime.cファイルのp9ctime関数内で行われています。この関数は、long型の時間値tを受け取り、それをsnprint関数を使ってフォーマットされた文字列bufに書き込む役割を担っています。

具体的には、snprint関数の引数として渡される曜日名と月名の部分で変更がありました。

変更前:

"SunMonTueWedThuFriSat" + (tm->tm_wday * 3),
"JanFebMarAprMayJunJulAugSepOctNovDec" + (tm->tm_mon * 3),

ここでは、文字列リテラル(例: "SunMonTueWedThuFriSat")に対して直接整数値(例: tm->tm_wday * 3)を加算しています。C言語では、文字列リテラルはcharの配列として扱われ、その名前は配列の先頭要素へのポインタに評価されます。したがって、この記述はポインタ演算として有効であり、文字列の先頭から指定されたオフセットだけ進んだ位置を指すポインタを生成します。例えば、tm->tm_wday0なら"Sun"の先頭、1なら"Mon"の先頭を指すポインタが得られます。

しかし、Clang 3.1のような一部のコンパイラは、文字列リテラルがchar *型に暗黙的に変換され、その結果として非constポインタとして扱われる可能性があるため、この形式に対して警告を発することがありました。コンパイラは、この非constポインタが後で変更される可能性があると解釈し、読み取り専用メモリに格納されている文字列リテラルを変更しようとする潜在的な未定義動作を防ぐために警告を発します。

変更後:

&"SunMonTueWedThuFriSat"[tm->tm_wday * 3],
&"JanFebMarAprMayJunJulAugSepOctNovDec"[tm->tm_mon * 3],

この変更では、文字列リテラルを配列として扱い、まずインデックス演算子[]を使用して特定の文字(例: "SunMonTueWedThuFriSat"[tm->tm_wday * 3])にアクセスしています。そして、その文字のアドレスをアドレス演算子&で取得しています。

この形式は、文字列リテラルがcharの配列として扱われることをより明示的に示し、結果として得られるポインタがconst char *型として適切に扱われるため、コンパイラが警告を発する可能性が低くなります。機能的には元のコードと全く同じ結果(目的の部分文字列の先頭へのポインタ)をもたらしますが、コンパイラにとってより安全で明確な記述と認識され、警告が抑制されます。

この修正は、コードの動作を変更することなく、コンパイラの警告を解消し、ビルドプロセスのクリーンさを保つためのものです。

関連リンク

  • Go言語のIssueトラッカー:
    • 現在のGoプロジェクトにおけるIssue 3534は、Mattermostの脆弱性に関するものであり、このコミットとは無関係です。当時のIssue 3534の具体的な内容は、公開されている情報からは特定できませんでした。
  • Clangコンパイラ:
  • Plan 9 from Bell Labs:

参考にした情報源リンク