display_errorsが謎の副作用を持っている箇所について

昨日 id:narusase さんよりお便りをいただきました。

はじめまして narusaseです。

↓このあたりに軽くまとめてありますが・・・
http://d.hatena.ne.jp/narusase/20091203
http://d.hatena.ne.jp/narusase/20091204

htmlspecialcharsのdisplay_errorsの解釈が逆転している問題があるようです。

UTF-8で下記のようなコードを書き実行すると

<?php
$str = 'メガネ';
$hoge = htmlspecialchars($str, ENT_QUOTES, 'SJIS');
printf("%s\n",var_export($hoge, true));
?>

htmlspecialcharsの箇所でエラーが起こります。

これ自体は問題ないのですがphp.iniの設定が「display_errors = Off」にした場合にPHP Warning.が発生します。
逆に、「display_errors = On」場合このエラーは表示されません。

これは、動作としては逆になるべきで誤っていると思います。

↓このあたりにdisplay_errors云々のそれっぽい報告はあるようですが、意図的にやっているので対応する気は無いと言っている様に見えます。
#・・・対応する気もなさそうだしこれも投げるだけ無駄なのかなぁ〜〜?
http://bugs.php.net/bug.php?id=47494
http://bugs.php.net/bug.php?id=49579

[http://ml.php.gr.jp/pipermail/php-dev/2009-December/001501.html:title=[PHP-dev 1500] htmlspecialcharsのdisplay_errorsの解釈が逆転している問題]

はい、これを htmlspecialchars() のバグと看做すには、まずは他の箇所で似たようなことをやっていないかどうかを確認し、それらの妥当性を検討しないといけません。

そこで探してみると…まあ、あることはあるようです。

main/php_variable.c (135-):

            if(++nest_level > PG(max_input_nesting_level)) {
                HashTable *ht;
                /* too many levels of nesting */

                if (track_vars_array) {
                    ht = Z_ARRVAL_P(track_vars_array);
                    zend_hash_del(ht, var, var_len + 1); 
                } else if (PG(register_globals)) {
                    ht = EG(active_symbol_table);
                    zend_hash_del(ht, var, var_len + 1); 
                }   

                zval_dtor(val);

                /* do not output the error message to the screen,
                 this helps us to to avoid "information disclosure" */
                if (!PG(display_errors)) {
                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Input variable nesting level exceeded %ld. To increase the limit change max_input_nesting_level in php.ini.", PG(max_input_nesting_level));
                }   
                efree(var_orig);
                retuk$a
            }   

これはリクエストパラメータで foo[]...= のように連想配列を指定したときに、何階層までのネストを許すかを設定する設定項目である max_input_nesting_level *1 をハンドルする箇所でございますが、さてコメントに注目。「information disclosure」って書いてあるけど一体なんの情報が流出するというの?ファイルアップロードでテンポラリファイルが作成できないときは普通にエラーが出るというのに。

で、次は json の拡張モジュールですか。

ext/json.c (298-):

    len = utf8_to_utf16(utf16, s, len);
    if (len <= 0) {
        if (utf16) {
            efree(utf16);
        }
        if (len < 0) {
            JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
            if (!PG(display_errors)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid UTF-8 sequence in argument");
            }
            smart_str_appendl(buf, "null", 4);
        } else {
            smart_str_appendl(buf, "\"\"", 2);
        }
        return;
    }

あちゃー、これもひどいですね。何を考えているんでしょう。

そして、今回ご指摘のあった htmlspecialchars() の実装がある ext/standard/html.c ですね。

ext/standard/html.c (1136-):

        if(status == FAILURE) {
            /* invalid MB sequence */
            if (quote_style & ENT_HTML_IGNORE_ERRORS) {
                continue;
            }
            efree(replaced);
            if(!PG(display_errors)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid multibyte sequence in argument");
            }
            *newlen = 0;
            return STR_EMPTY_ALLOC();
        }

さっきの json のにせよ、この if 文を付け加えた開発者は相当重度のアホとしかいいようがありません。svn blame & log を使ってアホを暴き出しましょう。

main/php_variables.c

236894      iliaa                               if (!PG(display_errors)) {
236894      iliaa                                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Input variable nesting level more than allowed %ld (change max_input_nesting_level in php.ini to increase the limit)", PG(max_input_nesting_level));
236894      iliaa                               }
236894      iliaa                               return;

ext/json/json.c:

251684       stas               if(len < 0) {
251684       stas                       if(!PG(display_errors)) {
251684       stas                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid UTF-8 sequence in argument");
251684       stas                       }
251684       stas               smart_str_appendl(buf, "null", 4);
251684       stas               } else {
251684       stas               smart_str_appendl(buf, "\"\"", 2);
251684       stas               }

ext/standard/html.c:

243425       stas               if(status == FAILURE) {
243425       stas                       /* invalid MB sequence */
243425       stas                       efree(replaced);
243425       stas                       if(!PG(display_errors)) {
243425       stas                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid multibyte sequence in argument");
243425       stas                       }
243425       stas                       *newlen = 0;
243425       stas                       return STR_EMPTY_ALLOC();
243425       stas               }

はああ。。。

*1:マニュアルにはなぜか 5.0.0 で削除されたと書いてあるけど...