てきとうなさいと べぇたばん

PHP 7.0.3で興味あったバグ トレイトでのメソッド名のバグ

恒例行事

mt_randでPHP界隈は騒がしいが、やはりPHP 7.0.3のChangelogを読んでもなんかおかしい。

mt_randについて

余談になるけど。結局いつmergeされるんでしょうか。

修正されたバグについて

いろいろありますが、興味深かったバグをピックアップ。

#71275 Bad method called on cloning an object having a trait

https://bugs.php.net/bug.php?id=71275

まーたtrait周りのバグかよーってくらいtraitのバグ多いなーという印象。

<?php

trait MyTrait {
    public function _() {
        throw new RuntimeException('Should not be called');
    }
}


class MyClass {
    use MyTrait;

    public function __clone() {
        echo 'I\'m working hard to clone';
    }
}


$instance = new MyClass();
clone $instance;

そして実行した結果。まずはPHP 7.0.3。

# php test71275.php
I'm working hard to clone

PHP 7.0.1。

# php test71275.php

Fatal error: Uncaught RuntimeException: Should not be called in /tmp/test71275.php:5
Stack trace:
#0 /tmp/test71275.php(20): MyClass->_()
#1 {main}
  thrown in /tmp/test71275.php on line 5

!?!?!?!?!?

diffを読んでみた。

古い方はこうなってる。

!strncmp(ZSTR_VAL(mname), ZEND_CLONE_FUNC_NAME, ZSTR_LEN(mname))

なるほど。ということは、mnameがZEND_CLONE_FUNC_NAMEとの比較がしたいのに、mnameの文字列長しか比較していなかったと。すなわち、mnameが「_」のとき、「__clone」との比較がしたいのに一文字しかないから「_」との比較になった結果、「_」がマッチしちゃうというわけか。以下の修正では、厳密に比較している。

zend_string_equals_literal(mname, ZEND_CLONE_FUNC_NAME)

この、zend_string_equals_literalは、Zend/zend_string.hにてプリプロセッサで定義されている。文字列長の数値とリテラルとの長さが同じかつ、メモリの中にあるsizeof(literal)-1バイト分同じですよということで正しく評価される。

#define zend_string_equals_literal(str, literal) \
        (ZSTR_LEN(str) == sizeof(literal)-1 && !memcmp(ZSTR_VAL(str), literal, sizeof(literal) - 1))

でも、traitだけなんだ?あれ?と思って継承とか使ってみたけど再現できんかった。実装が違うからそうなるのはしかたがないか。