$ 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