Skip to content

Commit 77dbc2c

Browse files
committed
ext/opcache: Mark dl()-created persistent strings as thread-local
During a request, opcache's interned-string handlers return a plain persistent string when the name is not already in shared memory. dl() registers a temporary module's function, class and property names this way, so under ZEND_RC_DEBUG the first refcount change on one of them (building an argument TypeError, ReflectionFunction::getName(), get_object_vars()) trips the persistent-string assertion. The strings belong to the request's temporary module; mark them GC_PERSISTENT_LOCAL, but only while zend_rc_debug is off, as dl() turns it off, so the check keeps covering strings that may later be shared. Closes GH-24421
1 parent 1fb0b45 commit 77dbc2c

5 files changed

Lines changed: 71 additions & 7 deletions

File tree

‎ext/dl_test/dl_test.stub.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public function test(string $str = ""): string {}
2020

2121
class DlTestSuperClass {
2222
public int $a;
23+
public int $temporary = 0;
2324
public function test(string $str = ""): string {}
2425
}
2526

‎ext/dl_test/dl_test_arginfo.h‎

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
try {
3+
dl_test_test2([]);
4+
} catch (TypeError $e) {
5+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
6+
}
7+
8+
try {
9+
(new DlTest)->test([]);
10+
} catch (TypeError $e) {
11+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
12+
}
13+
14+
var_dump((new ReflectionFunction('dl_test_test2'))->getName());
15+
16+
var_dump(get_object_vars(new DlTestSubClass));
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
dl() under opcache: refcounting names declared by a temporary module
3+
--SKIPIF--
4+
<?php include __DIR__ . "/skip.inc"; ?>
5+
--EXTENSIONS--
6+
opcache
7+
--INI--
8+
enable_dl=1
9+
opcache.enable_cli=1
10+
--FILE--
11+
<?php
12+
dl(PHP_OS_FAMILY === 'Windows' ? 'php_dl_test.dll' : 'dl_test.so');
13+
include __DIR__ . '/opcache_temporary_strings.inc';
14+
?>
15+
--EXPECT--
16+
TypeError: dl_test_test2(): Argument #1 ($str) must be of type string, array given
17+
TypeError: DlTest::test(): Argument #1 ($str) must be of type string, array given
18+
string(13) "dl_test_test2"
19+
array(1) {
20+
["temporary"]=>
21+
int(0)
22+
}

‎ext/opcache/ZendAccelerator.c‎

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,13 @@ static zend_string* ZEND_FASTCALL accel_new_interned_string_for_php(zend_string
578578
return ret;
579579
}
580580
}
581+
#if ZEND_RC_DEBUG
582+
/* PHP shouldn't create persistent strings during request, but at least
583+
* dl() may do this. They belong to the temporary module and are not shared. */
584+
if (!zend_rc_debug && !ZSTR_IS_INTERNED(str) && (GC_FLAGS(str) & GC_PERSISTENT)) {
585+
GC_MAKE_PERSISTENT_LOCAL(str);
586+
}
587+
#endif
581588
return str;
582589
}
583590

@@ -602,19 +609,31 @@ static zend_always_inline zend_string *accel_find_interned_string_ex(zend_ulong
602609

603610
static zend_string* ZEND_FASTCALL accel_init_interned_string_for_php(const char *str, size_t size, bool permanent)
604611
{
612+
zend_string *ret;
613+
605614
if (ZCG(counted)) {
606615
zend_ulong h = zend_inline_hash_func(str, size);
607-
zend_string *ret = accel_find_interned_string_ex(h, str, size);
616+
ret = accel_find_interned_string_ex(h, str, size);
608617

609-
if (!ret) {
610-
ret = zend_string_init(str, size, permanent);
611-
ZSTR_H(ret) = h;
618+
if (ret) {
619+
return ret;
612620
}
613621

614-
return ret;
622+
ret = zend_string_init(str, size, permanent);
623+
ZSTR_H(ret) = h;
624+
} else {
625+
ret = zend_string_init(str, size, permanent);
615626
}
616627

617-
return zend_string_init(str, size, permanent);
628+
#if ZEND_RC_DEBUG
629+
/* PHP shouldn't create persistent strings during request, but at least
630+
* dl() may do this. They belong to the temporary module and are not shared. */
631+
if (!zend_rc_debug && permanent) {
632+
GC_MAKE_PERSISTENT_LOCAL(ret);
633+
}
634+
#endif
635+
636+
return ret;
618637
}
619638

620639
static inline void accel_copy_permanent_list_types(

0 commit comments

Comments
 (0)