zend_string_dup 也有坑?

今天在写 Azalea 的时候,无意中遇到一个奇怪的 bug,代码片段如下

$testModel = $this->getModel('test');
$testModel->test();
var_dump('test');

getModel 是 Controller 方法用于获取 Model,$testModel 确实已经获取回来了,但是 $testModel->test() 出错了,同时 var_dump('test') 输出了 "Test",基本上定位到了 getModel方法中的把传入名称首字母大写而影响了 Model 类名的位置

zend_string *modelName, *name, *modelClass;

if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "S", &modelName) == FAILURE) {
    return;
}

name = zend_string_dup(modelName, 0);  // 复制一份传入的名称
ZSTR_VAL(name)[0] = toupper(ZSTR_VAL(name)[0]);  // 首字母大写
modelClass = strpprintf(0, "%sModel", ZSTR_VAL(name));  // 连接生成类名
zend_string_release(name);  // 释放 name

乍看没发现什么问题,最大嫌疑是 zend_string_dup 方法,跟踪进入源代码,

static zend_always_inline zend_string *zend_string_dup(zend_string *s, int persistent)
{
    if (ZSTR_IS_INTERNED(s)) {
        return s;
    } else {
        return zend_string_init(ZSTR_VAL(s), ZSTR_LEN(s), persistent);
    }
}

问题就出在 ZSTR_IS_INTERNED 宏,它判断了 zend_stirng 结构中的 gc.u.v.flags 值是否等于 IS_STR_INTERNED,即字符串是否为内部的。
因为 modelName 是指向参数栈传入的第一个字符串,因此并不会产生 zend_string_init

===== 更新 =====
这个坑罪魁祸首是 opcache,启动了 opcache 之后缓存了传入字符串 "test" 是放到了同一个内存块里,即 getModel('test')var_dump('test'),都是同一个 "test",如果禁用了 opcache 不会踩到该坑。

你可能感兴趣的:(zend_string_dup 也有坑?)