「?:」演算子
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のログを漁ってみると。。。あったよ(1・2)!ログが公開されていて本当に良かった。。。
コミット履歴で分かったのは「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デバッグは有害だって言われてるのに。。。そんなガッツしか見せられない自分に脱帽。_| ̄|○
こういう時、PHPのdebug_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も後で詳しく見てみるか。。。って、こういう大事なファイルは一番最初に参照しないと駄目じゃん。
で、最後の撃沈ですが、全てが終わった後にこれらの情報(1・2・3)を発見。。。2年前の記事かよ。_| ̄|○
もうなんか、駄目ダメって感じの要領が悪すぎる人間だなぁ。。。と再確認してしまいました。閃くのもスゲー遅いし。
ただ、自分が仮定していた内容はそんなに間違っていなかった事も再確認できました。
キーとなるdefine定義ですが「zend_language_parser.y」から直接渡されている事もある、という事で。
これからは、また違った視点でソースを見られる気がする(更に深追いできそう)。
そんなにいないと思いますが、自分と同じ位置で枕を濡らしていた人がいたら参考にしていただけば幸いです。