PHP Environment
opcache
The opcache extension mis-executes the ZEND_TYPE_CHECK opcode in at least
PHP 8.0.25 and causes a vital PHP expression, is_resource($x), to curiously
yield false even when $x is a valid resource. The issue occurs in the
same spot in grommunio-web, and is reliably reproducible, but we do not know
_why_ exactly. g-web is a big program and attempts to replicate the problem
with a smaller program have been unsuccessful to date.
mapi.so checks whether opcache.so is present and enabled, and if so, refuses operation. You must disable opcache.
Details:
We find that the Zend engine treats the PHP is_resource(...) function call
specially ([1]
<https://github.com/php/php-src/blob/master/Zend/zend_compile.c#L4497>_):
} else if (zend_string_equals_literal(lcname, "is_resource")) {
return zend_compile_func_typecheck(result, args, IS_RESOURCE);
and that Zend compiles it to a ZEND_TYPE_CHECK opcode with extended_value
being (1 << IS_RESOURCE) ([2]).
opline = zend_emit_op_tmp(result, ZEND_TYPE_CHECK, &arg_node, NULL);
if (type != _IS_BOOL) {
opline->extended_value = (1 << type);
} else {
opline->extended_value = (1 << IS_FALSE) | (1 << IS_TRUE);
}
When the two lines shown in the first block are removed, the
is_resource($x) expression would instead be compiled to a
ZEND_INIT_FCALL opcode ([3])
and execution would eventually land in the C function corresponding to
is_resource ([4]).
static inline void php_is_type(INTERNAL_FUNCTION_PARAMETERS, int type)
{
if (Z_TYPE_P(arg) == type) {
...
Summarizing our observations:
Unmodified Zend VM, php-opcache disabled,
is_resourcebecomesZEND_TYPE_CHECK: goodUnmodified Zend VM, php-opcache enabled,
is_resourcebecomesZEND_TYPE_CHECK: badModified Zend VM, php-opcache enabled,
is_resourcebecomesZEND_INIT_FCALL: goodWe conclude that php-opcache induces a problem with respect to the
ZEND_TYPE_CHECKopcode.
There is a... peculiar comment in php-opcache (MAY_BE_RESOURCE is the same
as 1 << IS_RESOURCE) ([5])
that could(?) be relevant:
case ZEND_TYPE_CHECK:
if (opline->extended_value == MAY_BE_RESOURCE) {
// TODO: support for is_resource() ???
break;
}