RubyのPrimeライブラリを利用する際に注意したいこと

___以下の内容はRuby2.2.3/Windows10で検証したものです。したがって違うバージョンやOSではまったく違う挙動を示す可能性があります。また既知の内容であればすみません……。___

# 今北産業

以下のようなコードはエラーも警告もでないので、個人的にはとても気を付けております。

```rb
# 上限が文字列でもエラーにならない!
g = Prime::EratosthenesGenerator.new
g.upper_bound = "foo"

# Prime#eachがジェネレータの上限を置き換えている!
Prime.each("hoge", g)
p g.upper_bound #=> "hoge"

# 上限が文字列にも関わらず普通に素数が生成できている!
arr = []
10.times{arr << g.next}
p arr #=> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

# 上限を数字に変えてみたものの、上限以上の素数を生成している!
g.upper_bound = 10
arr = []
10.times{arr << g.next}
p arr #=> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
p g.upper_bound #=> 10

# Prime#eachを利用してもダメでした……
primes = Prime.each(10)
arr = []
10.times{arr << primes.next}
p arr #=> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
```

# `Prime#each`は不用意に使わない

正直なところ使われる機会は多くないと思いますが、`Prime::PseudoPrimeGenerator`クラスには`upper_bound`および`upper_bound=`というメソッドが定義されており、素数の生成上限を取得設定することができます。実際的な素数生成にあたって利用する`Prime::EratosthenesGenerator`クラスなどは`Prime::PseudoPrimeGenerator`クラスのインスタンスなので、当然`upper_bound=`を呼び出して生成上限を設定することができます。

```rb
require "prime"

g = Prime::EratosthenesGenerator.new
g.upper_bound = 20
p g.each.to_a #=> [2, 3, 5, 7, 11, 13, 17, 19]

# 上限を設定しない場合 = upper_boundにnilを指定する場合
# 素数を無限に生成し続けるので、処理が終わらない
g.upper_bound = nil
p g.each.to_a
```
さてとりわけよく使われる`Prime#each`メソッドは生成上限とジェネレータの種類を指定して素数生成することができますが、このときジェネレータにも上限が設定されていた場合どうなるでしょうか?

```rb
g = Prime::EratosthenesGenerator.new
g.upper_bound = 20

p Prime.each(30, g).to_a #=> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
p g.upper_bound #=> 30
```

__端的にいうと`Prime#each`はとくに警告などなくジェネレータ側の上限を書き換えてしまいます。__ライブラリのソースコードを見たところ、内部的には`Prime#each`はそのジェネレータに対して`upper_bound=`を呼び出したのち、`Prime::PseudoPrimeGenerator#each`を利用して素数を生成するので、書き換えられてしまうということでした。

# `upper_bound=`は不用意に使わない

`Prime#each`が`upper_bound=`を呼び出すので書き換えられてしまうと述べましたが、この`upper_bound=`もまた曲者で、とくに入力バリデーションのようなことは行いません。__つまり与えられたデータが数値なのかどうかを判定せず、そのまま保持してしまうのです。__

```rb
g = Prime::EratosthenesGenerator.new

g.upper_bound = false # 問題なく代入できる
p g.each.to_a # 処理が終わらない

g.upper_bound = "123"
p g.each.to_a # エラー
```

`Prime::PseudoPrimeGenerator#each`は`upper_bound=`で与えられたデータ(より正確には上限値を保持するインスタンス変数`@ubound`の中身)が真なら上限まで素数を生成し、偽なら無限に素数を生成します。このため`g.upper_bound = false`とした場合、内部的には`nil`と同じ扱いになって処理が止まらなくなります。もっともこれは想定される挙動なのですが。また文字列を与えたときは上限を超えているかいないかの判定がうまくいかなくなり、エラーとなるわけです。

# `PrimePseudoPrimeGenerator#next`は不用意に使わない

`Prime::PseudoPrimeGenerator#each`やそれを内部的に呼び出す`Prime#each`を利用する場合は`@ubound`に妙なものが設定されていてもそれらしく動くので、問題ないといえば問題ないのですが、それ以外の方法――具体的には`Prime::PseudoPrimeGenerator#succ`もしくは`Prime::PrimePseudoGenerator#next`を利用して素数生成を行う場合は注意する必要があります。__たとえば以下の例では設定したはずの上限を超えて素数を生成してしまっています。__

```rb
g = Prime::EratosthenesGenerator.new
g.upper_bound = 10
p g.each.to_a #=> [2, 3, 5, 7] : これは正しい

arr = []
5.times{arr << g.next}
p arr #=> [2, 3, 5, 7, 11]
p g.upper_bound #=> 10
```

`Prime#each`は書き換えた上限まで素数を生成するという方法でしたが、今回の場合は上限値はまったく変わらないにもかかわらず、それ以上の素数を作ってしまっています。なぜこのようなことが起きるのでしょう? やはりライブラリのソースコードを見たところ、`next`や`succ`は`each`と違い、上限判定を行っていないためにこのような挙動を示すようです。__したがって`@ubound`が数字であろうとなかろうと素数が生成できてしまいまいます。__

```rb
g = Prime::EratosthenesGenerator.new
g.upper_bound = "hoge"

arr = []
5.times{arr << g.next}
p arr #=> [2, 3, 5, 7, 11]
p g.upper_bound #=> "hoge"
```

# `Prime#next`は不用意に使わない

ここまでの流れでもうお分かりかもしれませんが、`Prime#next`は`Prime::PseudoPrimeGenerator#next`を呼び出すだけです。したがって上限判定が行われることはなく、指定した上限以上の素数が生成できてしまいます。

```rb
primes = Prime.each(10)
arr = []
5.times{arr << primes.next}
p arr #=> [2, 3, 5, 7, 11]
```

`Prime#each`はブロックがあたえられた場合は生成した素数をそのままブロックに流し込むので、大きな問題にはなりづらいと思いますが、与えない場合は`next`や`succ`を使って無限に素数を生成することができてしまいます。

# まとめ

RubyのPrimeライブラリは高速で便利な一方、仕様が妙に複雑で、ともすれば予期せぬ動作を引き起こす可能性があります。とりわけ`next`を利用した場合、生成上限を定めていてもそれを無視して素数を無限に作り続けるという仕様は「極悪」で、セキュリティ上の問題になりかねません。Primeライブラリを使う場合はちょっとだけ注意を払うべきかもしれません。