PHP
型ジャグリングと非厳密比較の問題
サーチ…
タイプジャグリングとは何ですか?
PHPは緩やかに型付けされた言語です。つまり、デフォルトでは、式内のオペランドが同じ(または互換性のある)型である必要はありません。たとえば、文字列に数値を追加して、その文字列が機能することを期待できます。
var_dump ("This is example number " . 1);
出力は次のようになります。
string(24) "これは番号1の例です"
PHPは互換性のない変数型を、要求された操作が可能な型に自動的にキャストすることでこれを実現します。上のケースでは、整数リテラル1を文字列にキャストします。つまり、前の文字列リテラルに連結することができます。これは型ジャグリングと呼ばれます。これはPHPの非常に強力な機能ですが、気づいていないと髪を引っ張ってしまい、セキュリティ上の問題につながることもあります。
次の点を考慮してください。
if (1 == $variable) {
// do something
}
その意図は、プログラマが変数の値が1であることをチェックしているように見えますが、代わりに$ variableに "1と1/2"の値があればどうなりますか?答えはあなたを驚かせるかもしれません。
$variable = "1 and a half";
var_dump (1 == $variable);
結果は次のとおりです。
ブール(真)
なぜこれが起こったのですか? PHPは文字列 "1と1/2"が整数ではないことに気がついたが、それを整数1と比較するためにはそれをする必要があるからだ。PHPは失敗するのではなく、型ジャグリングを開始し、変数を整数。これは、文字列の先頭にあるすべての文字を整数にキャストしてキャストすることで行います。それは、数字として扱うことができない文字に遭遇するとすぐに停止する。したがって、「1と1/2」は整数1にキャストされます。
確かに、これは非常に工夫された例ですが、この問題を実証するのに役立ちます。次のいくつかの例では、実際のソフトウェアで起こったタイプジャグリングによるエラーに遭遇したいくつかのケースについて説明します。
ファイルからの読み込み
ファイルから読み込むとき、そのファイルの最後にいつ到達したかを知りたいと思っています。 fgets()
がファイルの最後にfalseを返すことを知ると、これをループの条件として使用することができます。しかし、最後の読み取りから返されたデータがboolean false
と評価される場合、ファイル読み出しループが早期に終了する可能性があります。
$handle = fopen ("/path/to/my/file", "r");
if ($handle === false) {
throw new Exception ("Failed to open file for reading");
}
while ($data = fgets($handle)) {
echo ("Current file line is $data\n");
}
fclose ($handle);
読み込まれるファイルに空白行が含まれている場合、その時点でwhile
ループが終了しfalse
。空の文字列はboolean false
評価されるためです。
代わりに厳密な等価演算子を使用して、ブール値のfalse
値を明示的にチェックできます。
while (($data = fgets($handle)) !== false) {
echo ("Current file line is $data\n");
}
これは実例であることに注意してください。実際には次のループを使用します。
while (!feof($handle)) {
$data = fgets($handle);
echo ("Current file line is $data\n");
}
または、すべてを次のように置き換えます。
$filedata = file("/path/to/my/file");
foreach ($filedata as $data) {
echo ("Current file line is $data\n");
}
スイッチの驚き
switch文は厳密でない比較を使用して一致を判定します。これはいくつかの厄介な驚きにつながる可能性があります。たとえば、次の文を考えてみましょう。
switch ($name) {
case 'input 1':
$mode = 'output_1';
break;
case 'input 2':
$mode = 'output_2';
break;
default:
$mode = 'unknown';
break;
}
これは非常に簡単なステートメントで、 $name
が文字列の場合は期待通りに動作しますが、それ以外の場合は問題を引き起こします。たとえば、 $name
が整数0
場合、比較中に型ジャグリングが発生します。ただし、case文のリテラル値はswitch文の条件ではなく、ジャグリングされます。文字列"input 1"
は、整数0
の入力値に一致する整数0
変換され0
。これは、整数0
値を指定すると、最初のケースは常に実行されます。
この問題の解決策はいくつかあります。
明示的なキャスト
比較の前に値を文字列に型キャストすることができます:
switch ((string)$name) {
...
}
または、文字列を返すことが知られている関数を使用することもできます。
switch (strval($name)) {
...
}
これらのメソッドはどちらも、値がcase
ステートメントの値と同じ型であることを保証します。
switch
避ける
if
文を使用すると、比較がどのように行われるかを制御でき、 厳密な比較演算子を使用できます。
if ($name === "input 1") {
$mode = "output_1";
} elseif ($name === "input 2") {
$mode = "output_2";
} else {
$mode = "unknown";
}
厳密なタイピング
PHP 7.0以降、タイプジャグリングの有害な影響のいくつかは、 厳密な型付けによって緩和することができます 。このdeclare
文をファイルの最初の行に含めることによって、PHPはTypeError
例外をスローすることによってパラメータ型宣言と型宣言を実行します。
declare(strict_types=1);
たとえば、このコードでは、パラメータ型定義を使用して、実行時にTypeError
型のキャッチ可能な例外がスローされます。
<?php
declare(strict_types=1);
function sum(int $a, int $b) {
return $a + $b;
}
echo sum("1", 2);
同様に、このコードでは戻り値の型宣言を使用しています。それは整数以外のものを返そうとすると例外をスローします:
<?php
declare(strict_types=1);
function returner($a): int {
return $a;
}
returner("this is a string");