Skip to content

Commit 8c92744

Browse files
committed
Merge branch 'PHP-7.4'
2 parents 1dc88ff + 60a7e60 commit 8c92744

File tree

12 files changed

+111
-48
lines changed

12 files changed

+111
-48
lines changed

Zend/tests/bug71818.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class MemoryLeak
1919
private $things = [];
2020
}
2121

22-
ini_set('memory_limit', '10M');
22+
ini_set('memory_limit', '20M');
2323

2424
for ($i = 0; $i < 100000; ++$i) {
2525
$obj = new MemoryLeak();

Zend/tests/bug72530.phpt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
Bug #72530: Use After Free in GC with Certain Destructors
3+
--FILE--
4+
<?php
5+
6+
class ryat {
7+
var $ryat;
8+
var $chtg;
9+
10+
function __destruct() {
11+
$this->chtg = $this->ryat;
12+
$this->ryat = 1;
13+
}
14+
}
15+
16+
$o = new ryat;
17+
$o->ryat = $o;
18+
$x =& $o->chtg;
19+
20+
unset($o);
21+
gc_collect_cycles();
22+
var_dump($x);
23+
24+
?>
25+
--EXPECT--
26+
object(ryat)#1 (2) {
27+
["ryat"]=>
28+
int(1)
29+
["chtg"]=>
30+
*RECURSION*
31+
}

Zend/tests/gc_011.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ $a->a = $a;
1515
var_dump($a);
1616
unset($a);
1717
var_dump(gc_collect_cycles());
18+
var_dump(gc_collect_cycles());
1819
echo "ok\n"
1920
?>
2021
--EXPECTF--
@@ -23,5 +24,6 @@ object(Foo)#%d (1) {
2324
*RECURSION*
2425
}
2526
__destruct
27+
int(0)
2628
int(1)
2729
ok

Zend/tests/gc_016.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ echo "ok\n"
2323
?>
2424
--EXPECT--
2525
-> int(0)
26-
int(1)
27-
int(1)
26+
int(0)
27+
int(2)
2828
ok

Zend/tests/gc_017.phpt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ unset($a);
3232
unset($b);
3333
unset($c);
3434
var_dump(gc_collect_cycles());
35+
var_dump(gc_collect_cycles());
3536
echo "ok\n"
3637
?>
3738
--EXPECTF--
3839
string(1) "%s"
3940
string(1) "%s"
4041
string(1) "%s"
41-
int(4)
42+
int(0)
43+
int(1)
4244
ok

Zend/tests/gc_028.phpt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ $bar->foo = $foo;
2828
unset($foo);
2929
unset($bar);
3030
var_dump(gc_collect_cycles());
31+
var_dump(gc_collect_cycles());
3132
?>
3233
--EXPECT--
33-
int(2)
34+
int(0)
35+
int(1)

Zend/tests/gc_029.phpt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ $bar->foo = $foo;
3030
unset($foo);
3131
unset($bar);
3232
var_dump(gc_collect_cycles());
33+
var_dump(gc_collect_cycles());
3334
?>
34-
--EXPECTREGEX--
35-
int\([23]\)
35+
--EXPECT--
36+
int(0)
37+
int(1)

Zend/tests/gc_035.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ var_dump(gc_collect_cycles());
2222
var_dump(gc_collect_cycles());
2323
--EXPECT--
2424
int(0)
25-
int(2)
2625
int(0)
26+
int(2)

Zend/tests/generators/bug76427.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ var_dump(gc_collect_cycles());
2121

2222
?>
2323
--EXPECT--
24-
int(4)
24+
int(2)

Zend/zend_gc.c

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
#define GC_ROOT 0x0 /* possible root of circular garbage */
142142
#define GC_UNUSED 0x1 /* part of linked list of unused buffers */
143143
#define GC_GARBAGE 0x2 /* garbage to delete */
144+
#define GC_DTOR_GARBAGE 0x3 /* garbage on which only the dtor should be invoked */
144145

145146
#define GC_GET_PTR(ptr) \
146147
((void*)(((uintptr_t)(ptr)) & ~GC_BITS))
@@ -151,9 +152,13 @@
151152
((((uintptr_t)(ptr)) & GC_BITS) == GC_UNUSED)
152153
#define GC_IS_GARBAGE(ptr) \
153154
((((uintptr_t)(ptr)) & GC_BITS) == GC_GARBAGE)
155+
#define GC_IS_DTOR_GARBAGE(ptr) \
156+
((((uintptr_t)(ptr)) & GC_BITS) == GC_DTOR_GARBAGE)
154157

155158
#define GC_MAKE_GARBAGE(ptr) \
156159
((void*)(((uintptr_t)(ptr)) | GC_GARBAGE))
160+
#define GC_MAKE_DTOR_GARBAGE(ptr) \
161+
((void*)(((uintptr_t)(ptr)) | GC_DTOR_GARBAGE))
157162

158163
/* GC address conversion */
159164
#define GC_IDX2PTR(idx) (GC_G(buf) + (idx))
@@ -1320,9 +1325,6 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
13201325
tail_call:
13211326
do {
13221327
if (root) {
1323-
GC_TRACE_REF(ref, "removing from buffer");
1324-
gc_remove_from_roots(root);
1325-
GC_REF_SET_INFO(ref, 0);
13261328
root = NULL;
13271329
count++;
13281330
} else if (GC_REF_ADDRESS(ref) != 0
@@ -1451,67 +1453,79 @@ ZEND_API int zend_gc_collect_cycles(void)
14511453
end = GC_G(first_unused);
14521454

14531455
if (gc_flags & GC_HAS_DESTRUCTORS) {
1454-
uint32_t *refcounts;
1455-
14561456
GC_TRACE("Calling destructors");
14571457

1458-
// TODO: may be use emalloc() ???
1459-
refcounts = pemalloc(sizeof(uint32_t) * end, 1);
1460-
1461-
/* Remember reference counters before calling destructors */
1458+
/* During a destructor call, new externally visible references to nested data may
1459+
* be introduced. These references can be introduced in a way that does not
1460+
* modify any refcounts, so we have no real way to detect this situation
1461+
* short of rerunning full GC tracing. What we do instead is to only run
1462+
* destructors at this point, and leave the actual freeing of the objects
1463+
* until the next GC run. */
1464+
1465+
/* Mark all roots for which a dtor will be invoked as DTOR_GARBAGE. Additionally
1466+
* color them purple. This serves a double purpose: First, they should be
1467+
* considered new potential roots for the next GC run. Second, it will prevent
1468+
* their removal from the root buffer by nested data removal. */
14621469
idx = GC_FIRST_ROOT;
14631470
current = GC_IDX2PTR(GC_FIRST_ROOT);
14641471
while (idx != end) {
14651472
if (GC_IS_GARBAGE(current->ref)) {
14661473
p = GC_GET_PTR(current->ref);
1467-
refcounts[idx] = GC_REFCOUNT(p);
1474+
if (GC_TYPE(p) == IS_OBJECT && !(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) {
1475+
zend_object *obj = (zend_object *) p;
1476+
if (obj->handlers->dtor_obj != zend_objects_destroy_object
1477+
|| obj->ce->destructor) {
1478+
current->ref = GC_MAKE_DTOR_GARBAGE(obj);
1479+
GC_REF_SET_COLOR(obj, GC_PURPLE);
1480+
} else {
1481+
GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
1482+
}
1483+
}
14681484
}
14691485
current++;
14701486
idx++;
14711487
}
14721488

1473-
/* Call destructors
1474-
*
1475-
* The root buffer might be reallocated during destructors calls,
1476-
* make sure to reload pointers as necessary. */
1489+
/* Remove nested data for objects on which a destructor will be called.
1490+
* This will not remove the objects themselves, as they have been colored
1491+
* purple. */
14771492
idx = GC_FIRST_ROOT;
1493+
current = GC_IDX2PTR(GC_FIRST_ROOT);
14781494
while (idx != end) {
1479-
current = GC_IDX2PTR(idx);
1480-
if (GC_IS_GARBAGE(current->ref)) {
1495+
if (GC_IS_DTOR_GARBAGE(current->ref)) {
14811496
p = GC_GET_PTR(current->ref);
1482-
if (GC_TYPE(p) == IS_OBJECT
1483-
&& !(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) {
1484-
zend_object *obj = (zend_object*)p;
1485-
1486-
GC_TRACE_REF(obj, "calling destructor");
1487-
GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
1488-
if (obj->handlers->dtor_obj != zend_objects_destroy_object
1489-
|| obj->ce->destructor) {
1490-
GC_ADDREF(obj);
1491-
obj->handlers->dtor_obj(obj);
1492-
GC_DELREF(obj);
1493-
}
1494-
}
1497+
count -= gc_remove_nested_data_from_buffer(p, current);
14951498
}
1499+
current++;
14961500
idx++;
14971501
}
14981502

1499-
/* Remove values captured in destructors */
1503+
/* Actually call destructors.
1504+
*
1505+
* The root buffer might be reallocated during destructors calls,
1506+
* make sure to reload pointers as necessary. */
15001507
idx = GC_FIRST_ROOT;
1501-
current = GC_IDX2PTR(GC_FIRST_ROOT);
15021508
while (idx != end) {
1503-
if (GC_IS_GARBAGE(current->ref)) {
1509+
current = GC_IDX2PTR(idx);
1510+
if (GC_IS_DTOR_GARBAGE(current->ref)) {
15041511
p = GC_GET_PTR(current->ref);
1505-
if (GC_REFCOUNT(p) > refcounts[idx]) {
1506-
count -= gc_remove_nested_data_from_buffer(p, current);
1512+
/* Mark this is as a normal root for the next GC run,
1513+
* it's no longer garbage for this run. */
1514+
current->ref = p;
1515+
/* Double check that the destructor hasn't been called yet. It could have
1516+
* already been invoked indirectly by some other destructor. */
1517+
if (!(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) {
1518+
zend_object *obj = (zend_object*)p;
1519+
GC_TRACE_REF(obj, "calling destructor");
1520+
GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
1521+
GC_ADDREF(obj);
1522+
obj->handlers->dtor_obj(obj);
1523+
GC_DELREF(obj);
15071524
}
15081525
}
1509-
current++;
15101526
idx++;
15111527
}
15121528

1513-
pefree(refcounts, 1);
1514-
15151529
if (GC_G(gc_protected)) {
15161530
/* something went wrong */
15171531
return 0;

ext/pcre/php_pcre.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
753753
return NULL;
754754
}
755755
_k = zend_string_init(ZSTR_VAL(BG(locale_string)), ZSTR_LEN(BG(locale_string)), 1);
756+
GC_MAKE_PERSISTENT_LOCAL(_k);
756757
zend_hash_add_ptr(&char_tables, _k, (void *)tables);
757758
zend_string_release(_k);
758759
}

ext/standard/basic_functions.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4549,10 +4549,19 @@ static void add_config_entries(HashTable *hash, zval *return_value);
45494549
static void add_config_entry(zend_ulong h, zend_string *key, zval *entry, zval *retval)
45504550
{
45514551
if (Z_TYPE_P(entry) == IS_STRING) {
4552+
zend_string *str = Z_STR_P(entry);
4553+
if (!ZSTR_IS_INTERNED(str)) {
4554+
if (!(GC_FLAGS(str) & GC_PERSISTENT)) {
4555+
zend_string_addref(str);
4556+
} else {
4557+
str = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0);
4558+
}
4559+
}
4560+
45524561
if (key) {
4553-
add_assoc_str_ex(retval, ZSTR_VAL(key), ZSTR_LEN(key), zend_string_copy(Z_STR_P(entry)));
4562+
add_assoc_str_ex(retval, ZSTR_VAL(key), ZSTR_LEN(key), str);
45544563
} else {
4555-
add_index_str(retval, h, zend_string_copy(Z_STR_P(entry)));
4564+
add_index_str(retval, h, str);
45564565
}
45574566
} else if (Z_TYPE_P(entry) == IS_ARRAY) {
45584567
zval tmp;

0 commit comments

Comments
 (0)