ます’s Blog - どうでもいい記事100選

どうでもいい記事100選

「?:」演算子

shimookaさんのトコで「?:」演算子が意外と使えないという事が判明したので、ちょっとガッカリな気分です。
問題は二つあって、

  • error_reportingがユルい設定の時にしか使う機会が無さそう
  • true or false判定が微妙すぎる

特に後者については、この演算子に限った話ではなく「==」演算子でも同様の問題を抱えています。
「0」がfalse判定されるので、自分では意図していなかった結果になるのが残念すぎる。。。個人的な理想としては、この演算子では「NULL or ""(空)」の時のみfalse判定してほしい。
とりあえず、後者の部分について改善してみた。

diff -urN php5.3-200808200430,orig/Zend/zend_execute.h php5.3-200808200430/Zend/zend_execute.h
--- php5.3-200808200430,orig/Zend/zend_execute.h	2008-08-13 03:35:34.000000000 +0900
+++ php5.3-200808200430/Zend/zend_execute.h	2008-08-20 18:02:33.000000000 +0900
@@ -132,6 +132,25 @@
 	return result;
 }
 
+static inline int i_zend_is_trues(zval *op)
+{
+	int result = 1;
+
+	switch (Z_TYPE_P(op)) {
+		case IS_NULL:
+			result = 0;
+			break;
+		case IS_STRING:
+			if (Z_STRLEN_P(op) == 0) {
+				result = 0;
+			}
+			break;
+		default:
+			break;
+	}
+	return result;
+}
+
 ZEND_API int zval_update_constant(zval **pp, void *arg TSRMLS_DC);
 ZEND_API int zval_update_constant_ex(zval **pp, void *arg, zend_class_entry *scope TSRMLS_DC);
 
diff -urN php5.3-200808200430,orig/Zend/zend_vm_def.h php5.3-200808200430/Zend/zend_vm_def.h
--- php5.3-200808200430,orig/Zend/zend_vm_def.h	2008-08-16 05:35:23.000000000 +0900
+++ php5.3-200808200430/Zend/zend_vm_def.h	2008-08-20 18:02:34.000000000 +0900
@@ -4119,7 +4119,7 @@
 	zend_free_op free_op1;
 	zval *value = GET_OP1_ZVAL_PTR(BP_VAR_R);
 
-	if (i_zend_is_true(value)) {
+	if (i_zend_is_trues(value)) {
 		EX_T(opline->result.u.var).tmp_var = *value;
 		zendi_zval_copy_ctor(EX_T(opline->result.u.var).tmp_var);
 		FREE_OP1();
diff -urN php5.3-200808200430,orig/Zend/zend_vm_execute.h php5.3-200808200430/Zend/zend_vm_execute.h
--- php5.3-200808200430,orig/Zend/zend_vm_execute.h	2008-08-16 05:35:29.000000000 +0900
+++ php5.3-200808200430/Zend/zend_vm_execute.h	2008-08-20 18:02:34.000000000 +0900
@@ -9019,7 +9019,7 @@
 	zend_free_op free_op1;
 	zval *value = _get_zval_ptr_var(&opline->op1, EX(Ts), &free_op1 TSRMLS_CC);
 
-	if (i_zend_is_true(value)) {
+	if (i_zend_is_trues(value)) {
 		EX_T(opline->result.u.var).tmp_var = *value;
 		zendi_zval_copy_ctor(EX_T(opline->result.u.var).tmp_var);
 		if (free_op1.var) {zval_ptr_dtor(&free_op1.var);};

パッチ適用前(php-orig)の実行結果とパッチ適用後(php-new)の実行結果は以下の通り。
従来の三項演算子は変更していません(「?:」演算子だけ変更)。

% cat ./newOperator_change.php
<?php

error_reporting( E_ALL );

$_GET["user_id2"] = "";
$_GET["user_id3"] = "0";
$_GET["user_id4"] = 0;
$_GET["user_id5"] = NULL;

$user_id1 = $_GET["user_id1"] ? $_GET["user_id1"] : "anonymous1";
$user_id2 = $_GET["user_id2"] ? $_GET["user_id2"] : "anonymous2";
$user_id3 = $_GET["user_id3"] ? $_GET["user_id3"] : "anonymous3";
$user_id4 = $_GET["user_id4"] ? $_GET["user_id4"] : "anonymous4";
$user_id5 = $_GET["user_id5"] ? $_GET["user_id5"] : "anonymous5";

echo "------------------------------------\n";
echo "1: "; var_dump( $user_id1 );
echo "2: "; var_dump( $user_id2 );
echo "3: "; var_dump( $user_id3 );
echo "4: "; var_dump( $user_id4 );
echo "5: "; var_dump( $user_id5 );

$user_id1 = $_GET["user_id1"] ?: "anonymous1";
$user_id2 = $_GET["user_id2"] ?: "anonymous2";
$user_id3 = $_GET["user_id3"] ?: "anonymous3";
$user_id4 = $_GET["user_id4"] ?: "anonymous4";
$user_id5 = $_GET["user_id5"] ?: "anonymous5";

echo "------------------------------------\n";
echo "1: "; var_dump( $user_id1 );
echo "2: "; var_dump( $user_id2 );
echo "3: "; var_dump( $user_id3 );
echo "4: "; var_dump( $user_id4 );
echo "5: "; var_dump( $user_id5 );

?>
% php-orig ./newOperator_change.php

Notice: Undefined index: user_id1 in ./newOperator_change.php on line 10
------------------------------------
1: string(10) "anonymous1"
2: string(10) "anonymous2"
3: string(10) "anonymous3"
4: string(10) "anonymous4"
5: string(10) "anonymous5"

Notice: Undefined index: user_id1 in ./newOperator_change.php on line 23
------------------------------------
1: string(10) "anonymous1"
2: string(10) "anonymous2"
3: string(10) "anonymous3"
4: string(10) "anonymous4"
5: string(10) "anonymous5"


% php-new ./newOperator_change.php

Notice: Undefined index: user_id1 in ./newOperator_change.php on line 10
------------------------------------
1: string(10) "anonymous1"
2: string(10) "anonymous2"
3: string(10) "anonymous3"
4: string(10) "anonymous4"
5: string(10) "anonymous5"

Notice: Undefined index: user_id1 in ./newOperator_change.php on line 23
------------------------------------
1: string(10) "anonymous1"
2: string(10) "anonymous2"
3: string(1) "0"
4: int(0)
5: string(10) "anonymous5"

Noticeが発生するのは切ないですが、少しだけ改善。わーい。
しかしながら(毎度の事ながら)影響範囲は不明。。。だったらやるなよな。
要するに、配列の初期化であればMugeSoさんのトコで紹介されている「+=」で配列の初期化をすればよいっていう事でした(ぉ


ここから以下は単なる作業(ハマり)履歴なので、だらだらと駄文が続きます。
ですので、暇な人か興味のある人が見ていただけると幸いです。<(_ _)>


果敢に挑んだバグ報告も見事に撃沈してしまったので、だったら自分で何とかしてみようか。。。と思ったのが事の始まりです。
いつものように、神様・仏様・grep様。。。。という事で頼みの綱であるgrep処理を敢行。
演算子。。。というか、言語の構造に関するものはZendのディレクトリだろ、と思っているので、Zendのディレクトリ以下で「?:」をキーワードにしてgrep処理。

% cd /usr/local/src
% gzip -dc ./php5.3-200808200430.tar.gz | tar zf -
% cd ./php5.3-200808200430/Zend
% grep -rn '?:' .
./tests/021.phpt:2:?: operator
./tests/021.phpt:5:var_dump(true ?: false);
./tests/021.phpt:6:var_dump(false ?: true);
./tests/021.phpt:7:var_dump(23 ?: 42);
./tests/021.phpt:8:var_dump(0 ?: "bar");
./tests/021.phpt:15:var_dump($a ?: $b);
./tests/021.phpt:16:var_dump($c ?: $d);
./tests/021.phpt:18:var_dump(1 ?: print(2));
./tests/021.phpt:23:$e['e'] = $e['e'] ?: 'e';

いきなり撃沈。。。_| ̄|○
なんか、今回は勝手が違うみたい。ぐすん。
「?」をキーワードにしてgrep処理しても凄い量の結果だし。。。さて、どうしようか。


ここで「テスト・プログラムの一番最初のコミット日時をWebの情報から探して、その日付をキーにしてWebのログから漁ればよいのかっ!」と、一つ目の電球が頭の上でピカンと開く。
グダグダな上に泥臭すぎるけど、今の自分(スキル)の想像力やカンでは精一杯の発想力。でも、ガッツがあれば作業できそう。今こそガッツを見せる時っ!
Webの情報では一番最初のコミット日時は「Tue Nov 20 23:56:45 2007 UTC」になっていたので、その期間内でWebのログを漁ってみると。。。あったよ()!ログが公開されていて本当に良かった。。。
コミット履歴で分かったのは「zend_language_parser.y」で定義されている事と、定義が「'?'」と「':'」で分割されている、という事。なので、grep処理で検索できなかったのね。納得。


一歩前進という事で、次に進む。
まだ良く分かっていないけど「zend_do_jmp_set」「zend_do_jmp_set_else」関数が、この演算子では重要な役割を演じているっぽい。
「zend_do_jmp_set」をキーワードにしてgrep処理を敢行。

% grep -rn 'zend_do_jmp_set' .
./zend_compile.c:4690:void zend_do_jmp_set(const znode *value, znode *jmp_token, znode *colon_token TSRMLS_DC)
./zend_compile.c:4709:void zend_do_jmp_set_else(znode *result, const znode *false_value, const znode *jmp_token, const znode *colon_token TSRMLS_DC)
./zend_language_parser.c:4411:    { zend_do_jmp_set(&(yyvsp[(1) - (3)]), &(yyvsp[(2) - (3)]), &(yyvsp[(3) - (3)]) TSRMLS_CC); }
./zend_language_parser.c:4416:    { zend_do_jmp_set_else(&(yyval), &(yyvsp[(5) - (5)]), &(yyvsp[(2) - (5)]), &(yyvsp[(3) - (5)]) TSRMLS_CC); }
./zend_compile.h:521:void zend_do_jmp_set(const znode *value, znode *jmp_token, znode *colon_token TSRMLS_DC);
./zend_compile.h:522:void zend_do_jmp_set_else(znode *result, const znode *false_value, const znode *jmp_token, const znode *colon_token TSRMLS_DC);
./zend_language_parser.y:629:   |       expr '?' ':' { zend_do_jmp_set(&$1, &$2, &$3 TSRMLS_CC); }
./zend_language_parser.y:630:           expr     { zend_do_jmp_set_else(&$$, &$5, &$2, &$3 TSRMLS_CC); }

関数は「zend_compile.c」で定義されている事が分かった。中身を覗いてみる。

% less -N ./zend_compile.c

        〜 省略 〜

   4690 void zend_do_jmp_set(const znode *value, znode *jmp_token, znode *colon_token TSRMLS_DC)
   4691 {
   4692         int op_number = get_next_op_number(CG(active_op_array));
   4693         zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
   4694
   4695         opline->opcode = ZEND_JMP_SET;
   4696         opline->result.op_type = IS_TMP_VAR;
   4697         opline->result.u.var = get_temporary_variable(CG(active_op_array));
   4698         opline->op1 = *value;
   4699         SET_UNUSED(opline->op2);
   4700
   4701         *colon_token = opline->result;
   4702
   4703         jmp_token->u.opline_num = op_number;
   4704
   4705         INC_BPC(CG(active_op_array));
   4706 }
   4707
   4708
   4709 void zend_do_jmp_set_else(znode *result, const znode *false_value, const znode *jmp_token, const znode *colon_token TSRMLS_DC)
   4710 {
   4711         zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
   4712
   4713         opline->opcode = ZEND_QM_ASSIGN;
   4714         opline->extended_value = 0;
   4715         opline->result = *colon_token;
   4716         opline->op1 = *false_value;
   4717         SET_UNUSED(opline->op2);
   4718
   4719         *result = opline->result;
   4720
   4721         CG(active_op_array)->opcodes[jmp_token->u.opline_num].op2.u.opline_num = get_next_op_number(CG(active_op_array));
   4722
   4723         DEC_BPC(CG(active_op_array));
   4724 }

        〜 省略 〜

どの関数も似たような実装になっているので訳が分からん。。。更に撃沈。_| ̄|○
良い感じで「追い詰めた!」と思っていたのに。。。とほほ。まだ何かある、という事ですか。


ここで(いきなり)過去を振り返る。
実は、PHPの言語構造や挙動を調べようとしたときに(いつも)ここで躓いていました。
実装が途切れている感じがする。。。というか、次に進むべき道が分からなくなってしまう感じがしていました。
煙に巻かれつつ「ココはお前のような軟弱な野郎が来るべき場所では無い」と言われている感じがして、枕を濡らす毎日でした。
opcode(Zend内部)の動作原理を全く理解していないツケがここにきて。。。という感じです。
でも、今回は何かが違いました。少しだけ強くなったのかもしれません。
「zend_compile.c」の他の実装を見ていると、多少の違いはあるけれど大体は同じような実装になっていました(フラグ情報みたいなものを入れて特定の関数を実行して終了、みたいな)。
ここで「であれば、このフラグ情報みたいなもので処理方法を判定しているのではなかろうか。。。」と、二つの目の電球が頭の上でピカンと開く。
この仮定を元に「ZEND_JMP_SET」をキーワードにしてgrep処理を敢行。

% grep -rn 'ZEND_JMP_SET' .
./zend_compile.c:4695:  opline->opcode = ZEND_JMP_SET;
./zend_vm_opcodes.h:154:#define ZEND_JMP_SET                         152
./zend_vm_execute.h:2286:static int ZEND_FASTCALL  ZEND_JMP_SET_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
./zend_vm_execute.h:5588:static int ZEND_FASTCALL  ZEND_JMP_SET_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
./zend_vm_execute.h:9016:static int ZEND_FASTCALL  ZEND_JMP_SET_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
./zend_vm_execute.h:22934:static int ZEND_FASTCALL  ZEND_JMP_SET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
./zend_vm_execute.h:34049:      ZEND_JMP_SET_SPEC_CONST_HANDLER,
./zend_vm_execute.h:34050:      ZEND_JMP_SET_SPEC_CONST_HANDLER,
./zend_vm_execute.h:34051:      ZEND_JMP_SET_SPEC_CONST_HANDLER,
./zend_vm_execute.h:34052:      ZEND_JMP_SET_SPEC_CONST_HANDLER,
./zend_vm_execute.h:34053:      ZEND_JMP_SET_SPEC_CONST_HANDLER,
./zend_vm_execute.h:34054:      ZEND_JMP_SET_SPEC_TMP_HANDLER,
./zend_vm_execute.h:34055:      ZEND_JMP_SET_SPEC_TMP_HANDLER,
./zend_vm_execute.h:34056:      ZEND_JMP_SET_SPEC_TMP_HANDLER,
./zend_vm_execute.h:34057:      ZEND_JMP_SET_SPEC_TMP_HANDLER,
./zend_vm_execute.h:34058:      ZEND_JMP_SET_SPEC_TMP_HANDLER,
./zend_vm_execute.h:34059:      ZEND_JMP_SET_SPEC_VAR_HANDLER,
./zend_vm_execute.h:34060:      ZEND_JMP_SET_SPEC_VAR_HANDLER,
./zend_vm_execute.h:34061:      ZEND_JMP_SET_SPEC_VAR_HANDLER,
./zend_vm_execute.h:34062:      ZEND_JMP_SET_SPEC_VAR_HANDLER,
./zend_vm_execute.h:34063:      ZEND_JMP_SET_SPEC_VAR_HANDLER,
./zend_vm_execute.h:34069:      ZEND_JMP_SET_SPEC_CV_HANDLER,
./zend_vm_execute.h:34070:      ZEND_JMP_SET_SPEC_CV_HANDLER,
./zend_vm_execute.h:34071:      ZEND_JMP_SET_SPEC_CV_HANDLER,
./zend_vm_execute.h:34072:      ZEND_JMP_SET_SPEC_CV_HANDLER,
./zend_vm_execute.h:34073:      ZEND_JMP_SET_SPEC_CV_HANDLER,
./zend_vm_def.h:4116:ZEND_VM_HANDLER(152, ZEND_JMP_SET, CONST|TMP|VAR|CV, ANY)
./zend_execute_API.c:1203:                      case ZEND_JMP_SET:
./zend_opcode.c:405:                    case ZEND_JMP_SET:

「zend_vm_opcodes.h」のdefine定義と「zend_vm_def.h」の定義が怪しい。
番号が一致している。。。これは偶然ではない気がする。「zend_vm_def.h」の中身を覗いてみる。

% less -N ./zend_vm_def.h

        〜 省略 〜

   4116 ZEND_VM_HANDLER(152, ZEND_JMP_SET, CONST|TMP|VAR|CV, ANY)
   4117 {
   4118         zend_op *opline = EX(opline);
   4119         zend_free_op free_op1;
   4120         zval *value = GET_OP1_ZVAL_PTR(BP_VAR_R);
   4121
   4122         if (i_zend_is_true(value)) {
   4123                 EX_T(opline->result.u.var).tmp_var = *value;
   4124                 zendi_zval_copy_ctor(EX_T(opline->result.u.var).tmp_var);
   4125                 FREE_OP1();
   4126 #if DEBUG_ZEND>=2
   4127                 printf("Conditional jmp to %d\n", opline->op2.u.opline_num);
   4128 #endif
   4129                 ZEND_VM_JMP(opline->op2.u.jmp_addr);
   4130         }
   4131
   4132         FREE_OP1();
   4133         ZEND_VM_NEXT_OPCODE();
   4134 }

        〜 省略 〜

「i_zend_is_true」関数なるものを発見。。。非常に怪しい。「i_zend_is_true」をキーワードにしてgrep処理を敢行。

% grep -rn 'i_zend_is_true' .
./zend_interfaces.c:157:                        result = i_zend_is_true(more);
./zend_vm_execute.h:1476:       int ret = i_zend_is_true(&opline->op1.u.constant);
./zend_vm_execute.h:1492:       int ret = i_zend_is_true(&opline->op1.u.constant);

        〜 省略 〜

./zend_execute.h:78:static inline int i_zend_is_true(zval *op)
./zend_vm_def.h:1673:   int ret = i_zend_is_true(GET_OP1_ZVAL_PTR(BP_VAR_R));
./zend_vm_def.h:1690:   int ret = i_zend_is_true(GET_OP1_ZVAL_PTR(BP_VAR_R));

        〜 省略 〜

./zend_object_handlers.c:114:           result = i_zend_is_true(retval) ? SUCCESS : FAILURE;
./zend_object_handlers.c:529:                   result = i_zend_is_true(retval);
./zend_object_handlers.c:534:                                   result = i_zend_is_true(retval);
./zend_object_handlers.c:1132:                                                  result = i_zend_is_true(rv);
./zend_execute_API.c:460:       return i_zend_is_true(op);

関数は「zend_execute.h」で定義されている事が分かった。中身を覗いてみる。

% less -N ./zend_execute.h

        〜 省略 〜

     78 static inline int i_zend_is_true(zval *op)
     79 {
     80         int result;
     81
     82         switch (Z_TYPE_P(op)) {
     83                 case IS_NULL:
     84                         result = 0;
     85                         break;
     86                 case IS_LONG:
     87                 case IS_BOOL:
     88                 case IS_RESOURCE:
     89                         result = (Z_LVAL_P(op)?1:0);
     90                         break;
     91                 case IS_DOUBLE:
     92                         result = (Z_DVAL_P(op) ? 1 : 0);
     93                         break;
     94                 case IS_STRING:
     95                         if (Z_STRLEN_P(op) == 0
     96                                 || (Z_STRLEN_P(op)==1 && Z_STRVAL_P(op)[0]=='0')) {
     97                                 result = 0;
     98                         } else {
     99                                 result = 1;
    100                         }
    101                         break;
    102                 case IS_ARRAY:
    103                         result = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0);
    104                         break;
    105                 case IS_OBJECT:
    106                         if(IS_ZEND_STD_OBJECT(*op)) {
    107                                 TSRMLS_FETCH();
    108
    109                                 if (Z_OBJ_HT_P(op)->cast_object) {
    110                                         zval tmp;
    111                                         if (Z_OBJ_HT_P(op)->cast_object(op, &tmp, IS_BOOL TSRMLS_CC) == SUCCESS) {
    112                                                 result = Z_LVAL(tmp);
    113                                                 break;
    114                                         }
    115                                 } else if (Z_OBJ_HT_P(op)->get) {
    116                                         zval *tmp = Z_OBJ_HT_P(op)->get(op TSRMLS_CC);
    117                                         if(Z_TYPE_P(tmp) != IS_OBJECT) {
    118                                                 /* for safety - avoid loop */
    119                                                 convert_to_boolean(tmp);
    120                                                 result = Z_LVAL_P(tmp);
    121                                                 zval_ptr_dtor(&tmp);
    122                                                 break;
    123                                         }
    124                                 }
    125                         }
    126                         result = 1;
    127                         break;
    128                 default:
    129                         result = 0;
    130                         break;
    131         }
    132         return result;
    133 }

        〜 省略 〜

一気に核心に触れた気がする。。。特に「case IS_STRING」の実装辺りとか。なんか、全てが繋がった気がする。
この関数内でprintfを仕込んでみると反応するっ!遂にキター!!昨日は、この時点で小躍り状態。
最初の方に掲載したパッチにある「i_zend_is_trues」関数を新たに用意し、「zend_vm_def.h」にある「ZEND_VM_HANDLER(152, ZEND_JMP_SET, CONST|TMP|VAR|CV, ANY)」内で呼ばれている「i_zend_is_true」関数を「i_zend_is_trues」関数に変更したところ。。。結果が変わらん。またしても撃沈。_| ̄|○


まだ駄目なの?うーん。どうしよう。。。と泣きそうになっていたのですが、最後のガッツを見せることにしました。
最後のガッツは「i_zend_is_true」関数を呼んでいる箇所全てにprintfを仕込むという荒業(約60箇所)。っていうか、printfデバッグは有害だって言われてるのに。。。そんなガッツしか見せられない自分に脱帽。_| ̄|○
こういう時、PHPdebug_backtrace関数は便利すぎる(glibcにはbacktrace関数があるみたいですけど)。。。と嘆きつつ、呼ばれている箇所を発見。
「zend_vm_execute.h」にある「static int ZEND_FASTCALL ZEND_JMP_SET_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)」の「i_zend_is_true」関数から呼ばれてました。
こちらの実装を「i_zend_is_trues」関数に変更したところ無事に反映されるようになりました。めでたし、めでたし。
にしても、どういう構成になっているんだろうか。。。「zend_vm_def.h」の存在意義が全然理解できていません。README.ZEND_VMを参照せよって事なのかな。
ついでにREADME.ZEND_MMとかOBJECTS2_HOWTOも後で詳しく見てみるか。。。って、こういう大事なファイルは一番最初に参照しないと駄目じゃん。


で、最後の撃沈ですが、全てが終わった後にこれらの情報()を発見。。。2年前の記事かよ。_| ̄|○
もうなんか、駄目ダメって感じの要領が悪すぎる人間だなぁ。。。と再確認してしまいました。閃くのもスゲー遅いし。
ただ、自分が仮定していた内容はそんなに間違っていなかった事も再確認できました。
キーとなるdefine定義ですが「zend_language_parser.y」から直接渡されている事もある、という事で。
これからは、また違った視点でソースを見られる気がする(更に深追いできそう)。
そんなにいないと思いますが、自分と同じ位置で枕を濡らしていた人がいたら参考にしていただけば幸いです。