$ php -r 'echo (0x00+2);echo "\n";' 4 $ php -r 'echo (0x00+ 2);echo "\n";' 2 $ php -r 'echo (0x00 + 2);echo "\n";' 2 $ php -r 'echo (0x00 +2);echo "\n";' 4 $ php -v PHP 5.3.8 (cli) (built: Dec 5 2011 21:24:09) Copyright (c) 1997-2011 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2011 Zend Technologies
なかなか面白い。以下のpatchでもうfixしているみたいだけども
古いコードはこっち。最新の5.3.10までバグっている。
<ST_IN_SCRIPTING>{HNUM} { char *hex = yytext + 2; /* Skip "0x" */ int len = yyleng - 2; /* Skip any leading 0s */ while (*hex == '0') { hex++; len--; } if (len < SIZEOF_LONG * 2 || (len == SIZEOF_LONG * 2 && *hex <= '7')) { zendlval->value.lval = strtol(hex, NULL, 16); zendlval->type = IS_LONG; return T_LNUMBER; } else { zendlval->value.dval = zend_hex_strtod(hex, NULL); zendlval->type = IS_DOUBLE; return T_DNUMBER; } }
yytextはマッチしている文字列を含むのだが、'\0'で終了しているわけではなく、長さyylengで切り出す必要があるみたい。
例えば、
echo(0x00+2);
を解析する。0x以降を解析する時にyytextは
0x00+2);
となり、まず'0x'をスキップし、その後0をスキップする。残りの文字列の
+2);
がstrtolに渡されて2と評価される。ここまでが1つのtokenとして評価され、その後'+','2'が評価されるので
0x2+2=2+2=4
となる。すなわち、0x00+nという文字列はnを16進数で評価したものと10進数で評価したものの足し算になる。
16進と10進とで別の値になる方がわかりやすい
$ php -r 'echo(0x00+10);echo("\n");' 26 $ php -r 'echo(0x00+11);echo("\n");' 28 $ php -r 'echo(0x00+100);echo("\n");' 356
となるのは
0x10+10=16+10=26 0x11+11=17+11=28 0x100+100=256+100=356
だから。
最初の引用で空白の位置で結果が変わるのはstrtolが
strtol("+2);", NULL, 16); // -> 2 strtol("+ 2);", NULL, 16); // -> 0 strtol(" +2);", NULL, 16); // -> 2 strtol(" + 2);", NULL, 16); // -> 0
と計算するためである。
同様に'-n'もstrtolで16進の-nに評価されるので
$ php -r 'echo(0x00-2);' -4 $ php -r 'echo(0x00-10);' -26
となる
'*n'や'/n'はstrtolで0になってしまうので普通に0と計算される
$ php -r 'echo(0x00*2);' 0 $ php -r 'echo(0x00*10);' 0 $ php -r 'echo(0x00/2);' 0 $ php -r 'echo(0x00/10);' 0