1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Add JIT guards for INIT_METHOD_CALL when the method may be modified (#8600)

Non-polymorphic methods can be modified from one request to an other due to recompilation or conditional declaration.

Fixes GH-8591

Co-authored-by: Oleg Stepanischev <Oleg.Stepanischev@tatar.ru>
This commit is contained in:
Arnaud Le Blanc
2022-05-27 13:15:15 +02:00
committed by GitHub
parent 6d4ebe7e0f
commit 69d263e2a1
15 changed files with 345 additions and 24 deletions

View File

@@ -8998,7 +8998,7 @@ static int zend_jit_init_method_call(dasm_State **Dst,
|2:
}
if (!func
if ((!func || zend_jit_may_be_modified(func, op_array))
&& trace
&& trace->op == ZEND_JIT_TRACE_INIT_CALL
&& trace->func
@@ -9006,7 +9006,7 @@ static int zend_jit_init_method_call(dasm_State **Dst,
int32_t exit_point;
const void *exit_addr;
exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_METHOD_CALL);
exit_point = zend_jit_trace_get_exit_point(opline, func ? ZEND_JIT_EXIT_INVALIDATE : ZEND_JIT_EXIT_METHOD_CALL);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;

View File

@@ -719,6 +719,26 @@ static zend_always_inline const zend_op* zend_jit_trace_get_exit_opline(zend_jit
return NULL;
}
static inline bool zend_jit_may_be_modified(const zend_function *func, const zend_op_array *called_from)
{
if (func->type == ZEND_INTERNAL_FUNCTION) {
#ifdef _WIN32
/* ASLR */
return 1;
#else
return 0;
#endif
} else if (func->type == ZEND_USER_FUNCTION) {
if (func->common.fn_flags & ZEND_ACC_PRELOADED) {
return 0;
}
if (func->op_array.filename == called_from->filename && !func->op_array.scope) {
return 0;
}
}
return 1;
}
static zend_always_inline bool zend_jit_may_be_polymorphic_call(const zend_op *opline)
{
if (opline->opcode == ZEND_INIT_FCALL

View File

@@ -366,26 +366,6 @@ static int zend_jit_trace_may_exit(const zend_op_array *op_array, const zend_op
return 0;
}
static bool zend_jit_may_be_modified(const zend_function *func, const zend_op_array *called_from)
{
if (func->type == ZEND_INTERNAL_FUNCTION) {
#ifdef _WIN32
/* ASLR */
return 1;
#else
return 0;
#endif
} else if (func->type == ZEND_USER_FUNCTION) {
if (func->common.fn_flags & ZEND_ACC_PRELOADED) {
return 0;
}
if (func->op_array.filename == called_from->filename && !func->op_array.scope) {
return 0;
}
}
return 1;
}
static zend_always_inline uint32_t zend_jit_trace_type_to_info_ex(zend_uchar type, uint32_t info)
{
if (type == IS_UNKNOWN) {

View File

@@ -9630,7 +9630,7 @@ static int zend_jit_init_method_call(dasm_State **Dst,
|2:
}
if (!func
if ((!func || zend_jit_may_be_modified(func, op_array))
&& trace
&& trace->op == ZEND_JIT_TRACE_INIT_CALL
&& trace->func
@@ -9641,7 +9641,7 @@ static int zend_jit_init_method_call(dasm_State **Dst,
int32_t exit_point;
const void *exit_addr;
exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_METHOD_CALL);
exit_point = zend_jit_trace_get_exit_point(opline, func ? ZEND_JIT_EXIT_INVALIDATE : ZEND_JIT_EXIT_METHOD_CALL);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;

View File

@@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
interface ModelInterface
{
}

View File

@@ -0,0 +1,49 @@
--TEST--
Bug GH-8591 001 (JIT does not account for class re-compile)
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=1M
opcache.jit=1255
opcache.file_update_protection=0
opcache.revalidate_freq=0
opcache.protect_memory=1
--FILE--
<?php
// Checks that JITed code does not crash in --repeat 2 after the ModelInterface
// interface is recompiled and Model is re-linked.
require __DIR__ . '/gh8591-001.inc';
class Model implements ModelInterface
{
protected static int $field = 1;
public function __construct()
{
for ($i = 0; $i < 10; $i++) {
$this->cast();
}
}
private function cast()
{
global $x;
$x = static::$field;
}
}
new Model();
// mark the file as changed (important)
touch(__DIR__ . '/gh8591-001.inc');
var_dump($x);
print "OK";
--EXPECT--
int(1)
OK

View File

@@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
interface ModelInterface
{
}

View File

@@ -0,0 +1,52 @@
--TEST--
Bug GH-8591 002 (JIT does not account for class re-compile)
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=1M
opcache.jit=1255
opcache.file_update_protection=0
opcache.revalidate_freq=0
opcache.protect_memory=1
--FILE--
<?php
// Checks that JITed code does not crash in --repeat 2 after the ModelInterface
// interface changes and Model is re-linked.
if (!isset(opcache_get_status()['scripts'][__DIR__ . '/gh8591-002.inc'])) {
require __DIR__ . '/gh8591-001.inc';
} else {
interface ModelInterace
{
}
}
class Model implements ModelInterface
{
protected static int $field = 1;
public function __construct()
{
for ($i = 0; $i < 10; $i++) {
$this->cast();
}
}
private function cast()
{
global $x;
$x = static::$field;
}
}
new Model();
var_dump($x);
print "OK";
--EXPECT--
int(1)
OK

View File

@@ -0,0 +1,45 @@
--TEST--
Bug GH-8591 003 (JIT does not account for class re-compile)
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=1M
opcache.jit=1255
opcache.file_update_protection=0
opcache.revalidate_freq=0
opcache.protect_memory=1
--FILE--
<?php
interface ModelInterface
{
}
class Model implements ModelInterface
{
protected static int $field = 1;
public function __construct()
{
for ($i = 0; $i < 10; $i++) {
$this->cast();
}
}
private function cast()
{
global $x;
$x = static::$field;
}
}
new Model();
var_dump($x);
print "OK";
--EXPECT--
int(1)
OK

View File

@@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
trait ModelTrait
{
}

View File

@@ -0,0 +1,51 @@
--TEST--
Bug GH-8591 004 (JIT does not account for class re-compile)
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=1M
opcache.jit=1255
opcache.file_update_protection=0
opcache.revalidate_freq=0
opcache.protect_memory=1
--FILE--
<?php
// Checks that JITed code does not crash in --repeat 2 after the ModelTrait
// trait is recompiled and Model is re-linked.
require __DIR__ . '/gh8591-004.inc';
class Model
{
use ModelTrait;
protected static int $field = 1;
public function __construct()
{
for ($i = 0; $i < 10; $i++) {
$this->cast();
}
}
private function cast()
{
global $x;
$x = static::$field;
}
}
new Model();
// mark the file as changed (important)
touch(__DIR__ . '/gh8591-004.inc');
var_dump($x);
print "OK";
--EXPECT--
int(1)
OK

View File

@@ -0,0 +1,12 @@
<?php
class AbstractModel
{
protected static int $field = 1;
final protected function cast()
{
global $x;
$x = static::$field;
}
}

View File

@@ -0,0 +1,41 @@
--TEST--
Bug GH-8591 001 (JIT does not account for class re-compile)
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=1M
opcache.jit=1255
opcache.file_update_protection=0
opcache.revalidate_freq=0
opcache.protect_memory=1
--FILE--
<?php
// Checks that JITed code does not crash in --repeat 2 after the AbstractModel
// class is recompiled and Model is re-linked.
require __DIR__ . '/gh8591-005.inc';
class Model extends AbstractModel
{
public function __construct()
{
for ($i = 0; $i < 10; $i++) {
$this->cast();
}
}
}
new Model();
// mark the file as changed (important)
touch(__DIR__ . '/gh8591-005.inc');
var_dump($x);
print "OK";
--EXPECT--
int(1)
OK

View File

@@ -0,0 +1,12 @@
<?php
class AbstractModel
{
protected static int $field = 1;
final protected function cast()
{
global $x;
$x = static::$field;
}
}

View File

@@ -0,0 +1,38 @@
--TEST--
Bug GH-8591 001 (JIT does not account for class re-compile)
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=1M
opcache.jit=1255
opcache.file_update_protection=0
opcache.revalidate_freq=0
opcache.protect_memory=1
opcache.preload={PWD}/gh8591-006.inc
--SKIPIF--
<?php
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
?>
--FILE--
<?php
class Model extends AbstractModel
{
public function __construct()
{
for ($i = 0; $i < 10; $i++) {
$this->cast();
}
}
}
new Model();
var_dump($x);
print "OK";
--EXPECT--
int(1)
OK