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
spuriously yield false even when $x is a valid resource. Spurious as
in: we do not know why, but it is reproducibly the same line in grommunio-web.
mapi.so checks whether opcache.so is loaded and refuses operation if this is the case. (This is in lieu of blocking opcache at the distribution packaging level from being installed, which sometimes is too much since FPM, CLI and SAPI can have different INI settings.)
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;
}