チュートリアル:ファジング入門

このチュートリアルでは、Goでのファジングの基本を説明します。ファジングでは、ランダムなデータがテストに対して実行され、脆弱性やクラッシュの原因となる入力を検出しようとします。ファジングによって検出できる脆弱性の例としては、SQLインジェクション、バッファオーバーフロー、サービス拒否攻撃、クロスサイトスクリプティング攻撃などがあります。

このチュートリアルでは、単純な関数のファズテストを作成し、goコマンドを実行し、コードのバグをデバッグして修正します。

このチュートリアル全体を通しての用語については、Goファジング用語集を参照してください。

次のセクションに進みます

  1. コード用のフォルダを作成します。
  2. テストするコードを追加します。
  3. 単体テストを追加します。
  4. ファズテストを追加します。
  5. 2つのバグを修正します。
  6. 追加のリソースを探ります。

注記:他のチュートリアルについては、チュートリアルを参照してください。

注記:Goファジングは現在、Goファジングドキュメントに記載されている組み込み型のサブセットをサポートしており、将来的にはより多くの組み込み型がサポートされる予定です。

前提条件

コード用のフォルダを作成する

まず、作成するコード用のフォルダを作成します。

  1. コマンドプロンプトを開き、ホームディレクトリに移動します。

    LinuxまたはMacの場合

    $ cd
    

    Windowsの場合

    C:\> cd %HOMEPATH%
    

    チュートリアルの残りの部分では、プロンプトとして$が表示されます。使用するコマンドはWindowsでも動作します。

  2. コマンドプロンプトから、コード用のディレクトリ「fuzz」を作成します。

    $ mkdir fuzz
    $ cd fuzz
    
  3. コードを保持するためのモジュールを作成します。

    go mod initコマンドを実行し、新しいコードのモジュールパスを指定します。

    $ go mod init example/fuzz
    go: creating new go.mod: module example/fuzz
    

    注記:本番コードの場合、独自のニーズにより具体的なモジュールパスを指定します。詳しくは、依存関係の管理を参照してください。

次に、後でファジングする文字列を反転する単純なコードを追加します。

テストするコードを追加

このステップでは、文字列を反転する関数を追加します。

コードを記述する

  1. テキストエディターを使用して、fuzzディレクトリにmain.goというファイルを作成します。

  2. main.goのファイルの先頭に、次のパッケージ宣言を貼り付けます。

    package main
    

    スタンドアロンプログラム(ライブラリではない)は常にパッケージmainにあります。

  3. パッケージ宣言の下に、次の関数宣言を貼り付けます。

    func Reverse(s string) string {
        b := []byte(s)
        for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
            b[i], b[j] = b[j], b[i]
        }
        return string(b)
    }
    

    この関数はstringを受け取り、一度に1byteずつループ処理し、最後に反転された文字列を返します。

    注記:このコードは、golang.org/x/example内のstringutil.Reverse関数に基づいています。

  4. main.goの先頭、パッケージ宣言の下に、文字列を初期化し、反転し、出力を印刷し、繰り返す次のmain関数を貼り付けます。

    func main() {
        input := "The quick brown fox jumped over the lazy dog"
        rev := Reverse(input)
        doubleRev := Reverse(rev)
        fmt.Printf("original: %q\n", input)
        fmt.Printf("reversed: %q\n", rev)
        fmt.Printf("reversed again: %q\n", doubleRev)
    }
    

    この関数はいくつかのReverse操作を実行してから、出力をコマンドラインに出力します。これは、コードの動作を確認したり、デバッグに役立つ場合があります。

  5. main関数はfmtパッケージを使用するため、インポートする必要があります。

    コードの先頭行は次のようになります

    package main
    
    import "fmt"
    

コードを実行する

main.goを含むディレクトリのコマンドラインから、コードを実行します。

$ go run .
original: "The quick brown fox jumped over the lazy dog"
reversed: "god yzal eht revo depmuj xof nworb kciuq ehT"
reversed again: "The quick brown fox jumped over the lazy dog"

元の文字列、それを反転した結果、そしてそれをもう一度反転した結果(元の文字列と等価)を確認できます。

コードが実行されるようになったので、テストする時です。

単体テストを追加

このステップでは、Reverse関数の基本的な単体テストを作成します。

コードを記述する

  1. テキストエディターを使用して、fuzzディレクトリにreverse_test.goというファイルを作成します。

  2. 次のコードをreverse_test.goに貼り付けます。

    package main
    
    import (
        "testing"
    )
    
    func TestReverse(t *testing.T) {
        testcases := []struct {
            in, want string
        }{
            {"Hello, world", "dlrow ,olleH"},
            {" ", " "},
            {"!12345", "54321!"},
        }
        for _, tc := range testcases {
            rev := Reverse(tc.in)
            if rev != tc.want {
                    t.Errorf("Reverse: %q, want %q", rev, tc.want)
            }
        }
    }
    

    この単純なテストでは、リストされた入力文字列が正しく反転されることをアサートします。

コードを実行する

go testを使用して単体テストを実行します

$ go test
PASS
ok      example/fuzz  0.013s

次に、単体テストをファズテストに変更します。

ファズテストを追加

単体テストには限界があります。つまり、各入力を開発者がテストに追加する必要があるということです。ファジングの利点の1つは、コードの入力を生成し、作成したテストケースでは到達しなかったエッジケースを特定できることです。

このセクションでは、より少ない作業でより多くの入力を生成できるように、単体テストをファズテストに変換します。

単体テスト、ベンチマーク、ファズテストを同じ*_test.goファイルに保持できますが、この例では単体テストをファズテストに変換します。

コードを記述する

テキストエディターで、reverse_test.goの単体テストを次のファズテストに置き換えます。

func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc)  // Use f.Add to provide a seed corpus
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev := Reverse(orig)
        doubleRev := Reverse(rev)
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}

ファジングにもいくつかの制限があります。単体テストでは、Reverse関数の予想される出力を予測し、実際の出力がその期待値を満たしていることを検証できました。

たとえば、テストケースReverse("Hello, world")では、単体テストは戻り値として"dlrow ,olleH"を指定しています。

ファジングの場合、入力を制御できないため、予想される出力を予測できません。

ただし、Reverse関数のいくつかのプロパティをファズテストで検証できます。このファズテストでチェックされている2つのプロパティは次のとおりです。

  1. 文字列を2回反転すると、元の値が保持されます
  2. 反転された文字列は、有効なUTF-8として状態を保持します。

単体テストとファズテストの構文の違いに注意してください

新しいパッケージunicode/utf8がインポートされていることを確認してください。

package main

import (
    "testing"
    "unicode/utf8"
)

単体テストをファズテストに変換したので、テストを再度実行します。

コードを実行する

  1. ファジングせずにFuzzReverseを実行して、シード入力がパスすることを確認します。

    $ go test
    PASS
    ok      example/fuzz  0.013s
    

    ファイルに他のテストがあり、ファズテストのみを実行する場合は、go test -run=FuzzReverseを実行することもできます。

  2. ファジングを使用してFuzzReverseを実行し、ランダムに生成された文字列入力が失敗を引き起こすかどうかを確認します。これは、新しいフラグ-fuzzをパラメーターFuzzに設定してgo testを使用して実行されます。以下のコマンドをコピーします。

    $ go test -fuzz=Fuzz
    

    もう1つの便利なフラグは-fuzztimeで、ファジングにかかる時間を制限します。たとえば、以下のテストで-fuzztime 10sを指定すると、それまでに失敗が発生しない限り、デフォルトで10秒経過後にテストが終了します。このセクションcmd/goドキュメントを参照して、他のテストフラグを確認してください。

    コピーしたコマンドを実行します。

    $ go test -fuzz=Fuzz
    fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
    fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers
    fuzz: minimizing 38-byte failing input file...
    --- FAIL: FuzzReverse (0.01s)
        --- FAIL: FuzzReverse (0.00s)
            reverse_test.go:20: Reverse produced invalid UTF-8 string "\x9c\xdd"
    
        Failing input written to testdata/fuzz/FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
        To re-run:
        go test -run=FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
    FAIL
    exit status 1
    FAIL    example/fuzz  0.030s
    

    ファジング中に失敗が発生し、問題の原因となった入力が、go testが呼び出された次のときにも(-fuzzフラグがなくても)実行されるシードコーパスファイルに書き込まれます。失敗の原因となった入力を表示するには、テキストエディターでtestdata/fuzz/FuzzReverseディレクトリに書き込まれたコーパスファイルを開きます。シードコーパスファイルには異なる文字列が含まれている場合がありますが、形式は同じです。

    go test fuzz v1
    string("泃")
    

    コーパスファイルの最初の行は、エンコードバージョンを示しています。後続の各行は、コーパスエントリを構成する各タイプの値を表しています。ファズターゲットは1つの入力しか受け取らないため、バージョン後に値は1つだけです。

  3. -fuzzフラグなしでgo testを再度実行します。新しい失敗したシードコーパスエントリが使用されます

    $ go test
    --- FAIL: FuzzReverse (0.00s)
        --- FAIL: FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a (0.00s)
            reverse_test.go:20: Reverse produced invalid string
    FAIL
    exit status 1
    FAIL    example/fuzz  0.016s
    

    テストに失敗したので、デバッグする時です。

無効な文字列エラーを修正する

このセクションでは、失敗をデバッグし、バグを修正します。

先に進む前に、自分でこの問題について考え、解決策を試してみてください。

エラーの診断

このエラーをデバッグするにはいくつかの方法があります。VS Codeをテキストエディターとして使用している場合は、デバッガーを設定して調査できます。

このチュートリアルでは、便利なデバッグ情報をターミナルにログ出力します。

まず、utf8.ValidStringのドキュメントを参照してください。

ValidString reports whether s consists entirely of valid UTF-8-encoded runes.

現在のReverse関数は文字列をバイト単位で反転するため、そこに問題があります。元の文字列のUTF-8でエンコードされたルーンを保持するには、文字列をルーン単位で反転する必要があります。

入力(この場合は中国語の文字)が反転されたときにReverseが無効な文字列を生成する理由を調べるために、反転された文字列のルーン数を検査できます。

コードを記述する

テキストエディターで、FuzzReverse内のファズターゲットを次のように置き換えます。

f.Fuzz(func(t *testing.T, orig string) {
    rev := Reverse(orig)
    doubleRev := Reverse(rev)
    t.Logf("Number of runes: orig=%d, rev=%d, doubleRev=%d", utf8.RuneCountInString(orig), utf8.RuneCountInString(rev), utf8.RuneCountInString(doubleRev))
    if orig != doubleRev {
        t.Errorf("Before: %q, after: %q", orig, doubleRev)
    }
    if utf8.ValidString(orig) && !utf8.ValidString(rev) {
        t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
    }
})

エラーが発生した場合、または-vオプションを付けてテストを実行した場合、このt.Logf行はコマンドラインに出力されます。これは、この特定の問題のデバッグに役立ちます。

コードを実行する

go testコマンドを使用してテストを実行してください。

$ go test
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)
        reverse_test.go:16: Number of runes: orig=1, rev=3, doubleRev=1
        reverse_test.go:21: Reverse produced invalid UTF-8 string "\x83\xb3\xe6"
FAIL
exit status 1
FAIL    example/fuzz    0.598s

使用されたシードコーパス全体では、すべての文字が1バイトの文字列を使用していました。しかし、泃のような文字は複数のバイトを必要とする場合があります。そのため、バイト単位で文字列を反転すると、マルチバイト文字が無効になります。

注記: Goが文字列をどのように処理するかに興味がある場合は、ブログ記事Goにおける文字列、バイト、rune、文字を読んで、より深く理解してください。

バグをよりよく理解した上で、Reverse関数のエラーを修正してください。

エラーを修正してください。

Reverse関数を修正するには、バイト単位ではなく、rune単位で文字列を走査しましょう。

コードを記述する

テキストエディタで、既存のReverse()関数を以下で置き換えてください。

func Reverse(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

主な違いは、Reverseが文字列の各byteではなく、各runeを反復処理するようになったことです。

コードを実行する

  1. go testコマンドを使用してテストを実行してください。

    $ go test
    PASS
    ok      example/fuzz  0.016s
    

    テストがパスしました!

  2. go test -fuzzを使用して再度ファジングし、新しいバグがないか確認してください。

    $ go test -fuzz=Fuzz
    fuzz: elapsed: 0s, gathering baseline coverage: 0/37 completed
    fuzz: minimizing 506-byte failing input file...
    fuzz: elapsed: 0s, gathering baseline coverage: 5/37 completed
    --- FAIL: FuzzReverse (0.02s)
        --- FAIL: FuzzReverse (0.00s)
            reverse_test.go:33: Before: "\x91", after: "�"
    
        Failing input written to testdata/fuzz/FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c
        To re-run:
        go test -run=FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c
    FAIL
    exit status 1
    FAIL    example/fuzz  0.032s
    

    2回反転させた後、文字列が元の文字列と異なることがわかります。今回は入力自体が無効なUnicodeです。文字列でファジングしているのに、なぜこれが可能なのでしょうか?

    再度デバッグしましょう。

二重反転エラーを修正してください。

このセクションでは、二重反転の失敗をデバッグし、バグを修正します。

先に進む前に、自分でこの問題について考え、解決策を試してみてください。

エラーの診断

これまでと同様に、この失敗をデバッグするにはいくつかの方法があります。この場合は、デバッガを使用するのが良い方法です。

このチュートリアルでは、Reverse関数に役立つデバッグ情報をログに出力します。

反転された文字列をよく見て、エラーを見つけましょう。Goでは、文字列はバイトの読み取り専用スライスであり、有効なUTF-8ではないバイトを含むことができます。元の文字列は1バイト'\x91'のバイトスライスです。入力文字列が[]runeに設定されると、GoはバイトスライスをUTF-8にエンコードし、バイトをUTF-8文字�に置き換えます。置換されたUTF-8文字と入力バイトスライスを比較すると、明らかに等しくありません。

コードを記述する

  1. テキストエディタで、Reverse関数を以下で置き換えてください。

    func Reverse(s string) string {
        fmt.Printf("input: %q\n", s)
        r := []rune(s)
        fmt.Printf("runes: %q\n", r)
        for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
            r[i], r[j] = r[j], r[i]
        }
        return string(r)
    }
    

    これにより、文字列をruneのスライスに変換する際に何が間違っているのかを理解するのに役立ちます。

コードを実行する

今回は、ログを検査するために、失敗したテストだけを実行します。そのため、go test -runを使用します。

FuzzXxx/testdata内の特定のコーパスエントリを実行するには、-runに{FuzzTestName}/{filename}を指定できます。これはデバッグ時に役立ちます。この場合は、-runフラグを失敗したテストの正確なハッシュに設定します。ターミナルから一意のハッシュをコピーして貼り付けてください。これは以下のものとは異なります。

$ go test -run=FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0
input: "\x91"
runes: ['�']
input: "�"
runes: ['�']
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)
        reverse_test.go:16: Number of runes: orig=1, rev=1, doubleRev=1
        reverse_test.go:18: Before: "\x91", after: "�"
FAIL
exit status 1
FAIL    example/fuzz    0.145s

入力が無効なUnicodeであることを知っているので、Reverse関数内のエラーを修正しましょう。

エラーを修正してください。

この問題を修正するために、Reverseへの入力が有効なUTF-8でない場合はエラーを返しましょう。

コードを記述する

  1. テキストエディタで、既存のReverse関数を以下で置き換えてください。

    func Reverse(s string) (string, error) {
        if !utf8.ValidString(s) {
            return s, errors.New("input is not valid UTF-8")
        }
        r := []rune(s)
        for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
            r[i], r[j] = r[j], r[i]
        }
        return string(r), nil
    }
    

    この変更により、入力文字列に有効なUTF-8ではない文字が含まれている場合にエラーが返されます。

  2. Reverse関数は現在エラーを返すため、余分なエラー値を破棄するようにmain関数を変更します。既存のmain関数を以下で置き換えてください。

    func main() {
        input := "The quick brown fox jumped over the lazy dog"
        rev, revErr := Reverse(input)
        doubleRev, doubleRevErr := Reverse(rev)
        fmt.Printf("original: %q\n", input)
        fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
        fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
    }
    

    入力文字列が有効なUTF-8であるため、これらのReverseへの呼び出しはnilエラーを返すはずです。

  3. errorsパッケージとunicode/utf8パッケージをインポートする必要があります。main.goのインポート文は以下のようになります。

    import (
        "errors"
        "fmt"
        "unicode/utf8"
    )
    
  4. reverse_test.goファイルを修正してエラーをチェックし、エラーが発生した場合はテストをスキップして返します。

    func FuzzReverse(f *testing.F) {
        testcases := []string {"Hello, world", " ", "!12345"}
        for _, tc := range testcases {
            f.Add(tc)  // Use f.Add to provide a seed corpus
        }
        f.Fuzz(func(t *testing.T, orig string) {
            rev, err1 := Reverse(orig)
            if err1 != nil {
                return
            }
            doubleRev, err2 := Reverse(rev)
            if err2 != nil {
                 return
            }
            if orig != doubleRev {
                t.Errorf("Before: %q, after: %q", orig, doubleRev)
            }
            if utf8.ValidString(orig) && !utf8.ValidString(rev) {
                t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
            }
        })
    }
    

    返す代わりに、t.Skip()を呼び出して、そのファズ入力の実行を停止することもできます。

コードを実行する

  1. go testコマンドを使用してテストを実行してください。

    $ go test
    PASS
    ok      example/fuzz  0.019s
    
  2. go test -fuzz=Fuzzを使用してファジングし、数秒後、ctrl-Cでファジングを停止します。ファズテストは、失敗した入力に遭遇するまで実行されます(-fuzztimeフラグを渡さない限り)。失敗が発生しない場合は無期限に実行され、ctrl-Cでプロセスを中断できます。

$ go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/38 completed
fuzz: elapsed: 0s, gathering baseline coverage: 38/38 completed, now fuzzing with 4 workers
fuzz: elapsed: 3s, execs: 86342 (28778/sec), new interesting: 2 (total: 35)
fuzz: elapsed: 6s, execs: 193490 (35714/sec), new interesting: 4 (total: 37)
fuzz: elapsed: 9s, execs: 304390 (36961/sec), new interesting: 4 (total: 37)
...
fuzz: elapsed: 3m45s, execs: 7246222 (32357/sec), new interesting: 8 (total: 41)
^Cfuzz: elapsed: 3m48s, execs: 7335316 (31648/sec), new interesting: 8 (total: 41)
PASS
ok      example/fuzz  228.000s
  1. go test -fuzz=Fuzz -fuzztime 30sを使用してファジングします。これは、失敗が見つからない場合、30秒後に終了します。

    $ go test -fuzz=Fuzz -fuzztime 30s
    fuzz: elapsed: 0s, gathering baseline coverage: 0/5 completed
    fuzz: elapsed: 0s, gathering baseline coverage: 5/5 completed, now fuzzing with 4 workers
    fuzz: elapsed: 3s, execs: 80290 (26763/sec), new interesting: 12 (total: 12)
    fuzz: elapsed: 6s, execs: 210803 (43501/sec), new interesting: 14 (total: 14)
    fuzz: elapsed: 9s, execs: 292882 (27360/sec), new interesting: 14 (total: 14)
    fuzz: elapsed: 12s, execs: 371872 (26329/sec), new interesting: 14 (total: 14)
    fuzz: elapsed: 15s, execs: 517169 (48433/sec), new interesting: 15 (total: 15)
    fuzz: elapsed: 18s, execs: 663276 (48699/sec), new interesting: 15 (total: 15)
    fuzz: elapsed: 21s, execs: 771698 (36143/sec), new interesting: 15 (total: 15)
    fuzz: elapsed: 24s, execs: 924768 (50990/sec), new interesting: 16 (total: 16)
    fuzz: elapsed: 27s, execs: 1082025 (52427/sec), new interesting: 17 (total: 17)
    fuzz: elapsed: 30s, execs: 1172817 (30281/sec), new interesting: 17 (total: 17)
    fuzz: elapsed: 31s, execs: 1172817 (0/sec), new interesting: 17 (total: 17)
    PASS
    ok      example/fuzz  31.025s
    

    ファジングがパスしました!

    -fuzzフラグに加えて、いくつかの新しいフラグがgo testに追加されており、ドキュメントで確認できます。

    ファジング出力で使用されている用語の詳細については、Goファジングを参照してください。「新しい興味深い」とは、既存のファズテストコーパスのコードカバレッジを拡大する入力を指します。「新しい興味深い」入力の数は、ファジング開始時に急激に増加し、新しいコードパスが発見されると数回スパイクし、その後時間とともに減少することが予想されます。

結論

素晴らしい!Goでのファジングを体験しました。

次のステップは、ファズしたいコード内の関数を選択して試してみることです!ファジングによってコードのバグが見つかった場合は、トロフィーケースに追加することを検討してください。

問題が発生した場合、または機能に関するアイデアがある場合は、問題を報告してください。

機能に関する議論や一般的なフィードバックについては、Gophers Slackの#fuzzingチャンネルに参加することもできます。

詳細については、go.dev/security/fuzzのドキュメントをご覧ください。

完成したコード

— main.go —

package main

import (
    "errors"
    "fmt"
    "unicode/utf8"
)

func main() {
    input := "The quick brown fox jumped over the lazy dog"
    rev, revErr := Reverse(input)
    doubleRev, doubleRevErr := Reverse(rev)
    fmt.Printf("original: %q\n", input)
    fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
    fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
}

func Reverse(s string) (string, error) {
    if !utf8.ValidString(s) {
        return s, errors.New("input is not valid UTF-8")
    }
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r), nil
}

— reverse_test.go —

package main

import (
    "testing"
    "unicode/utf8"
)

func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc) // Use f.Add to provide a seed corpus
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev, err1 := Reverse(orig)
        if err1 != nil {
            return
        }
        doubleRev, err2 := Reverse(rev)
        if err2 != nil {
            return
        }
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}

トップへ戻る