今天在写 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 不会踩到该坑。
网友评论