diff --git a/Zend/tests/__debugInfo_reference.phpt b/Zend/tests/debug_info/__debugInfo_reference.phpt similarity index 100% rename from Zend/tests/__debugInfo_reference.phpt rename to Zend/tests/debug_info/__debugInfo_reference.phpt diff --git a/Zend/tests/debug_info/debug_info-error-0.0.phpt b/Zend/tests/debug_info/debug_info-error-0.0.phpt index ab41b440fc888..8967bf258bb65 100644 --- a/Zend/tests/debug_info/debug_info-error-0.0.phpt +++ b/Zend/tests/debug_info/debug_info-error-0.0.phpt @@ -17,4 +17,8 @@ $c = new C(0.0); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-0.phpt b/Zend/tests/debug_info/debug_info-error-0.phpt index 37289c6468fff..293981aede96d 100644 --- a/Zend/tests/debug_info/debug_info-error-0.phpt +++ b/Zend/tests/debug_info/debug_info-error-0.phpt @@ -17,4 +17,8 @@ $c = new C(0); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-1.0.phpt b/Zend/tests/debug_info/debug_info-error-1.0.phpt index 9b168621b5efe..651cbde6ab378 100644 --- a/Zend/tests/debug_info/debug_info-error-1.0.phpt +++ b/Zend/tests/debug_info/debug_info-error-1.0.phpt @@ -17,4 +17,8 @@ $c = new C(1.0); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-1.phpt b/Zend/tests/debug_info/debug_info-error-1.phpt index ae01a6055c004..96629413eb0cb 100644 --- a/Zend/tests/debug_info/debug_info-error-1.phpt +++ b/Zend/tests/debug_info/debug_info-error-1.phpt @@ -17,4 +17,8 @@ $c = new C(1); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-debug_zval_dump-basic.phpt b/Zend/tests/debug_info/debug_info-error-debug_zval_dump-basic.phpt new file mode 100644 index 0000000000000..0aef96586286a --- /dev/null +++ b/Zend/tests/debug_info/debug_info-error-debug_zval_dump-basic.phpt @@ -0,0 +1,25 @@ +--TEST-- +Testing __debugInfo() magic method with bad returns scalar (debug_zval_dump) +--FILE-- +val; + } + public function __construct($val) { + $this->val = $val; + } +} + +$c = new C(true); +debug_zval_dump($c); + +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): debug_zval_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-debug_zval_dump-within-array.phpt b/Zend/tests/debug_info/debug_info-error-debug_zval_dump-within-array.phpt new file mode 100644 index 0000000000000..d1a1ea39b1f72 --- /dev/null +++ b/Zend/tests/debug_info/debug_info-error-debug_zval_dump-within-array.phpt @@ -0,0 +1,28 @@ +--TEST-- +Testing __debugInfo() magic method with bad returns scalar inside an array (debug_zval_dump) +--FILE-- +val; + } + public function __construct($val) { + $this->val = $val; + } +} + +$a = [ + 'foo', + new C(true), + 'bar', +]; +debug_zval_dump($a); +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): debug_zval_dump(Array) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-debug_zval_dump-within-object.phpt b/Zend/tests/debug_info/debug_info-error-debug_zval_dump-within-object.phpt new file mode 100644 index 0000000000000..c7d1a0af924e0 --- /dev/null +++ b/Zend/tests/debug_info/debug_info-error-debug_zval_dump-within-object.phpt @@ -0,0 +1,27 @@ +--TEST-- +Testing __debugInfo() magic method with bad returns scalar inside an object (debug_zval_dump) +--FILE-- +val; + } + public function __construct($val) { + $this->val = $val; + } +} + +$o = new stdClass(); +$o->foo = 'foo'; +$o->c = new C(true); +$o->bar = 'bar'; +debug_zval_dump($o); +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): debug_zval_dump(Object(stdClass)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-empty_str.phpt b/Zend/tests/debug_info/debug_info-error-empty_str.phpt index bbab78cd82021..2c1d8578b7bec 100644 --- a/Zend/tests/debug_info/debug_info-error-empty_str.phpt +++ b/Zend/tests/debug_info/debug_info-error-empty_str.phpt @@ -17,4 +17,8 @@ $c = new C(""); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-false.phpt b/Zend/tests/debug_info/debug_info-error-false.phpt index 3e48372c420c2..2e7475d6c3506 100644 --- a/Zend/tests/debug_info/debug_info-error-false.phpt +++ b/Zend/tests/debug_info/debug_info-error-false.phpt @@ -17,4 +17,8 @@ $c = new C(false); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-object.phpt b/Zend/tests/debug_info/debug_info-error-object.phpt index 42e073999908c..c867e83f08ac8 100644 --- a/Zend/tests/debug_info/debug_info-error-object.phpt +++ b/Zend/tests/debug_info/debug_info-error-object.phpt @@ -17,4 +17,8 @@ $c = new C(new stdClass); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-print_r-basic.phpt b/Zend/tests/debug_info/debug_info-error-print_r-basic.phpt new file mode 100644 index 0000000000000..391e88932a725 --- /dev/null +++ b/Zend/tests/debug_info/debug_info-error-print_r-basic.phpt @@ -0,0 +1,25 @@ +--TEST-- +Testing __debugInfo() magic method with bad returns scalar (print_r) +--FILE-- +val; + } + public function __construct($val) { + $this->val = $val; + } +} + +$c = new C(true); +print_r($c); + +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): print_r(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-print_r-within-array.phpt b/Zend/tests/debug_info/debug_info-error-print_r-within-array.phpt new file mode 100644 index 0000000000000..7eb2b7fff0c20 --- /dev/null +++ b/Zend/tests/debug_info/debug_info-error-print_r-within-array.phpt @@ -0,0 +1,28 @@ +--TEST-- +Testing __debugInfo() magic method with bad returns scalar inside an array (print_r) +--FILE-- +val; + } + public function __construct($val) { + $this->val = $val; + } +} + +$a = [ + 'foo', + new C(true), + 'bar', +]; +print_r($a); +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): print_r(Array) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-print_r-within-object.phpt b/Zend/tests/debug_info/debug_info-error-print_r-within-object.phpt new file mode 100644 index 0000000000000..68f01f862fcf8 --- /dev/null +++ b/Zend/tests/debug_info/debug_info-error-print_r-within-object.phpt @@ -0,0 +1,27 @@ +--TEST-- +Testing __debugInfo() magic method with bad returns scalar inside an object (print_r) +--FILE-- +val; + } + public function __construct($val) { + $this->val = $val; + } +} + +$o = new stdClass(); +$o->foo = 'foo'; +$o->c = new C(true); +$o->bar = 'bar'; +print_r($o); +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): print_r(Object(stdClass)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-resource.phpt b/Zend/tests/debug_info/debug_info-error-resource.phpt index ccacce7a74b45..62e73984fa6ef 100644 --- a/Zend/tests/debug_info/debug_info-error-resource.phpt +++ b/Zend/tests/debug_info/debug_info-error-resource.phpt @@ -19,4 +19,8 @@ $c = new C(fopen("data:text/plain,Foo", 'r')); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-str.phpt b/Zend/tests/debug_info/debug_info-error-str.phpt index 85d3f63b97e05..391cf514423c5 100644 --- a/Zend/tests/debug_info/debug_info-error-str.phpt +++ b/Zend/tests/debug_info/debug_info-error-str.phpt @@ -17,4 +17,8 @@ $c = new C("foo"); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-true.phpt b/Zend/tests/debug_info/debug_info-error-true.phpt index 3843c6a7a14a5..f0bb2718340ae 100644 --- a/Zend/tests/debug_info/debug_info-error-true.phpt +++ b/Zend/tests/debug_info/debug_info-error-true.phpt @@ -17,4 +17,8 @@ $c = new C(true); var_dump($c); ?> --EXPECTF-- -Fatal error: __debuginfo() must return an array in %s on line %d +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-within-array.phpt b/Zend/tests/debug_info/debug_info-error-within-array.phpt new file mode 100644 index 0000000000000..87414b379fd9f --- /dev/null +++ b/Zend/tests/debug_info/debug_info-error-within-array.phpt @@ -0,0 +1,28 @@ +--TEST-- +Testing __debugInfo() magic method with bad returns scalar inside an array +--FILE-- +val; + } + public function __construct($val) { + $this->val = $val; + } +} + +$a = [ + 'foo', + new C(true), + 'bar', +]; +var_dump($a); +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Array) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-error-within-object.phpt b/Zend/tests/debug_info/debug_info-error-within-object.phpt new file mode 100644 index 0000000000000..e8dfd823c6716 --- /dev/null +++ b/Zend/tests/debug_info/debug_info-error-within-object.phpt @@ -0,0 +1,27 @@ +--TEST-- +Testing __debugInfo() magic method with bad returns scalar inside an object +--FILE-- +val; + } + public function __construct($val) { + $this->val = $val; + } +} + +$o = new stdClass(); +$o->foo = 'foo'; +$o->c = new C(true); +$o->bar = 'bar'; +var_dump($o); +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: __debuginfo() must return an array in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(stdClass)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-null-deprecation-promoted.phpt b/Zend/tests/debug_info/debug_info-null-deprecation-promoted.phpt new file mode 100644 index 0000000000000..cb5e2b8255407 --- /dev/null +++ b/Zend/tests/debug_info/debug_info-null-deprecation-promoted.phpt @@ -0,0 +1,30 @@ +--TEST-- +Testing __debugInfo() magic method with deprecated return null +--FILE-- + +--EXPECTF-- +object(Bar)#2 (0) { +} + +Fatal error: Uncaught Exception: Returning null from Bar::__debugInfo() is deprecated, return an empty array instead in %s:%d +Stack trace: +#0 [internal function]: {closure:%s:%d}(8192, 'Returning null ...', '%s', %d) +#1 %s(%d): var_dump(Object(Bar)) +#2 {main} + thrown in %s on line %d diff --git a/Zend/tests/debug_info/debug_info-null.phpt b/Zend/tests/debug_info/debug_info-null.phpt new file mode 100644 index 0000000000000..d9cba939c7777 --- /dev/null +++ b/Zend/tests/debug_info/debug_info-null.phpt @@ -0,0 +1,20 @@ +--TEST-- +Testing __debugInfo() magic method with deprecated return null +--FILE-- + +--EXPECTF-- +Deprecated: Returning null from Bar::__debugInfo() is deprecated, return an empty array instead in %s on line %d +object(Bar)#%d (0) { +} diff --git a/Zend/tests/debug_info/debug_info.phpt b/Zend/tests/debug_info/debug_info.phpt index bff5876777ba2..c961b5c44efac 100644 --- a/Zend/tests/debug_info/debug_info.phpt +++ b/Zend/tests/debug_info/debug_info.phpt @@ -13,22 +13,12 @@ class Foo { } } -class Bar { - public $val = 123; - - public function __debugInfo() { - return null; - } -} - $f = new Foo; var_dump($f); -$b = new Bar; -var_dump($b); ?> ---EXPECTF-- -object(Foo)#%d (3) { +--EXPECT-- +object(Foo)#1 (3) { ["a"]=> int(1) ["b":protected]=> @@ -36,7 +26,3 @@ object(Foo)#%d (3) { ["c":"Foo":private]=> int(3) } - -Deprecated: Returning null from Bar::__debugInfo() is deprecated, return an empty array instead in %s on line %d -object(Bar)#%d (0) { -} diff --git a/Zend/zend.c b/Zend/zend.c index 4d024444a4be9..474d39c6934f5 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -375,9 +375,9 @@ ZEND_API zend_string *zend_strpprintf_unchecked(size_t max_len, const char *form } /* }}} */ -static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent); +static bool zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent); -static void print_hash(smart_str *buf, HashTable *ht, int indent, bool is_object) /* {{{ */ +static bool print_hash(smart_str *buf, HashTable *ht, int indent, bool is_object) /* {{{ */ { zval *tmp; zend_string *string_key; @@ -398,7 +398,7 @@ static void print_hash(smart_str *buf, HashTable *ht, int indent, bool is_object if (is_object) { const char *prop_name, *class_name; size_t prop_len; - int mangled = zend_unmangle_property_name_ex(string_key, &class_name, &prop_name, &prop_len); + zend_result mangled = zend_unmangle_property_name_ex(string_key, &class_name, &prop_name, &prop_len); smart_str_appendl(buf, prop_name, prop_len); if (class_name && mangled == SUCCESS) { @@ -417,7 +417,9 @@ static void print_hash(smart_str *buf, HashTable *ht, int indent, bool is_object smart_str_append_long(buf, num_key); } smart_str_appends(buf, "] => "); - zend_print_zval_r_to_buf(buf, tmp, indent+PRINT_ZVAL_INDENT); + if (UNEXPECTED(!zend_print_zval_r_to_buf(buf, tmp, indent+PRINT_ZVAL_INDENT))) { + return false; + } smart_str_appends(buf, "\n"); } ZEND_HASH_FOREACH_END(); indent -= PRINT_ZVAL_INDENT; @@ -425,6 +427,7 @@ static void print_hash(smart_str *buf, HashTable *ht, int indent, bool is_object smart_str_appendc(buf, ' '); } smart_str_appends(buf, ")\n"); + return true; } /* }}} */ @@ -541,29 +544,28 @@ ZEND_API void zend_print_flat_zval_r(zval *expr) smart_str_free(&buf); } -static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /* {{{ */ +static bool zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /* {{{ */ { switch (Z_TYPE_P(expr)) { - case IS_ARRAY: + case IS_ARRAY: { smart_str_appends(buf, "Array\n"); if (!(GC_FLAGS(Z_ARRVAL_P(expr)) & GC_IMMUTABLE)) { if (GC_IS_RECURSIVE(Z_ARRVAL_P(expr))) { smart_str_appends(buf, " *RECURSION*"); - return; + return true; } GC_PROTECT_RECURSION(Z_ARRVAL_P(expr)); } - print_hash(buf, Z_ARRVAL_P(expr), indent, false); + bool status = print_hash(buf, Z_ARRVAL_P(expr), indent, false); GC_TRY_UNPROTECT_RECURSION(Z_ARRVAL_P(expr)); - break; + return status; + } case IS_OBJECT: { - HashTable *properties; - zend_object *zobj = Z_OBJ_P(expr); uint32_t *guard = zend_get_recursion_guard(zobj); zend_string *class_name = Z_OBJ_HANDLER_P(expr, get_class_name)(zobj); - smart_str_appends(buf, ZSTR_VAL(class_name)); + smart_str_append(buf, class_name); zend_string_release_ex(class_name, 0); if (!(zobj->ce->ce_flags & ZEND_ACC_ENUM)) { @@ -579,37 +581,37 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /* if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) { smart_str_appends(buf, " *RECURSION*"); - return; + return true; } - if ((properties = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_DEBUG)) == NULL) { - print_hash(buf, (HashTable*) &zend_empty_array, indent, true); - break; + HashTable *properties = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_DEBUG); + /* Either we must have properties as a HashTable (even if empty) or an exception if NULL is returned */ + ZEND_ASSERT(properties || EG(exception)); + if (UNEXPECTED(properties == NULL)) { + return false; } ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj); - print_hash(buf, properties, indent, true); + bool status = print_hash(buf, properties, indent, true); ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj); - zend_release_properties(properties); - break; + zend_array_release(properties); + return status; } case IS_LONG: smart_str_append_long(buf, Z_LVAL_P(expr)); - break; + return true; case IS_REFERENCE: - zend_print_zval_r_to_buf(buf, Z_REFVAL_P(expr), indent); - break; + return zend_print_zval_r_to_buf(buf, Z_REFVAL_P(expr), indent); case IS_STRING: smart_str_append(buf, Z_STR_P(expr)); - break; - default: - { - zend_string *str = zval_get_string_func(expr); - smart_str_append(buf, str); - zend_string_release_ex(str, 0); - } - break; + return true; + default: { + zend_string *str = zval_get_string_func(expr); + smart_str_append(buf, str); + zend_string_release_ex(str, 0); + return true; + } } } /* }}} */ @@ -617,7 +619,11 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /* ZEND_API zend_string *zend_print_zval_r_to_str(zval *expr, int indent) /* {{{ */ { smart_str buf = {0}; - zend_print_zval_r_to_buf(&buf, expr, indent); + bool status = zend_print_zval_r_to_buf(&buf, expr, indent); + if (UNEXPECTED(!status)) { + smart_str_free(&buf); + return NULL; + } smart_str_0(&buf); return buf.s; } @@ -626,6 +632,10 @@ ZEND_API zend_string *zend_print_zval_r_to_str(zval *expr, int indent) /* {{{ */ ZEND_API void zend_print_zval_r(zval *expr, int indent) /* {{{ */ { zend_string *str = zend_print_zval_r_to_str(expr, indent); + /* If an exception was triggered while printing the zval */ + if (UNEXPECTED(str == NULL)) { + return; + } zend_write(ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release_ex(str, 0); } diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index cba95810ba496..8c89812577279 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -183,6 +183,9 @@ ZEND_METHOD(SensitiveParameterValue, __debugInfo) static HashTable *attributes_sensitive_parameter_value_get_properties_for(zend_object *zobj, zend_prop_purpose purpose) { + if (purpose == ZEND_PROP_PURPOSE_DEBUG) { + return (HashTable*)&zend_empty_array; + } return NULL; } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 470fb76ec14e1..2c822f1a9856e 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -229,9 +229,9 @@ ZEND_API HashTable *zend_std_get_debug_info(zend_object *object, int *is_temp) / return ht; } - zend_error_noreturn(E_ERROR, ZEND_DEBUGINFO_FUNC_NAME "() must return an array"); - - return NULL; /* Compilers are dumb and don't understand that noreturn means that the function does NOT need a return value... */ + zval_ptr_dtor(&retval); + zend_type_error(ZEND_DEBUGINFO_FUNC_NAME "() must return an array"); + return NULL; } /* }}} */ diff --git a/ext/dom/node.c b/ext/dom/node.c index de80dba202e99..ea9685c969470 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -469,7 +469,8 @@ zend_result dom_node_owner_document_read(dom_object *obj, zval *retval) xmlDocPtr docp = nodep->doc; if (!docp) { - return FAILURE; + ZVAL_NULL(retval); + return SUCCESS; } php_dom_create_object((xmlNodePtr) docp, retval, obj); diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 3619eaef12a5f..6a3f88738f3e6 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -523,7 +523,9 @@ static HashTable* dom_get_debug_info_helper(zend_object *object, int *is_temp) / ZEND_ASSERT(string_key != NULL); if (entry->read_func(obj, &value) == FAILURE) { - continue; + zend_array_release(debug_info); + debug_info = NULL; + goto exit; } if (Z_TYPE(value) == IS_OBJECT) { @@ -535,6 +537,7 @@ static HashTable* dom_get_debug_info_helper(zend_object *object, int *is_temp) / zend_hash_update(debug_info, string_key, &value); } ZEND_HASH_FOREACH_END(); +exit: zend_string_release_ex(object_str, false); DOM_G(suppress_warnings) = false; diff --git a/ext/dom/tests/DOMEntityReference_predefined_free.phpt b/ext/dom/tests/DOMEntityReference_predefined_free.phpt index 46e54e1b7d308..717448d97cda7 100644 --- a/ext/dom/tests/DOMEntityReference_predefined_free.phpt +++ b/ext/dom/tests/DOMEntityReference_predefined_free.phpt @@ -8,7 +8,7 @@ $ref = new DOMEntityReference("amp"); var_dump($ref); ?> --EXPECTF-- -object(DOMEntityReference)#1 (17) { +object(DOMEntityReference)#1 (18) { ["nodeName"]=> string(3) "amp" ["nodeValue"]=> @@ -33,6 +33,8 @@ object(DOMEntityReference)#1 (17) { NULL ["isConnected"]=> bool(false) + ["ownerDocument"]=> + NULL ["namespaceURI"]=> NULL ["prefix"]=> diff --git a/ext/dom/tests/delayed_freeing/without_contructor.phpt b/ext/dom/tests/delayed_freeing/without_contructor.phpt index 4a5f63ed9accf..1f2c7aa3e3099 100644 --- a/ext/dom/tests/delayed_freeing/without_contructor.phpt +++ b/ext/dom/tests/delayed_freeing/without_contructor.phpt @@ -31,8 +31,6 @@ try { ?> --EXPECT-- -object(DOMNode)#2 (0) { -} Invalid State Error Couldn't fetch DOMNode Couldn't fetch DOMNode diff --git a/ext/dom/tests/dom_node_debugInfo.phpt b/ext/dom/tests/dom_node_debugInfo.phpt new file mode 100644 index 0000000000000..f0a2d836f8076 --- /dev/null +++ b/ext/dom/tests/dom_node_debugInfo.phpt @@ -0,0 +1,17 @@ +--TEST-- +DOMNode custom __debugInfo() should abort at the first exception +--EXTENSIONS-- +dom +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught DOMException: Invalid State Error in %s:%d +Stack trace: +#0 %s(%d): var_dump(Object(DOMNode)) +#1 {main} + thrown in %s on line %d diff --git a/ext/dom/tests/gh16316.phpt b/ext/dom/tests/gh16316.phpt index 43368746bc462..1c4b8dead2990 100644 --- a/ext/dom/tests/gh16316.phpt +++ b/ext/dom/tests/gh16316.phpt @@ -24,9 +24,5 @@ try { ?> --EXPECT-- -object(Demo)#1 (1) { - ["registerNodeNamespaces"]=> - bool(true) -} Invalid State Error Invalid State Error diff --git a/ext/dom/tests/modern/spec/Document_implementation_createDocumentType.phpt b/ext/dom/tests/modern/spec/Document_implementation_createDocumentType.phpt index bfb150b5cca89..53ce0536cebfe 100644 --- a/ext/dom/tests/modern/spec/Document_implementation_createDocumentType.phpt +++ b/ext/dom/tests/modern/spec/Document_implementation_createDocumentType.phpt @@ -25,7 +25,7 @@ foreach ($test_matrix as $test_item) { ?> --EXPECT-- -object(Dom\DocumentType)#3 (19) { +object(Dom\DocumentType)#3 (20) { ["name"]=> string(5) "qname" ["entities"]=> @@ -46,6 +46,8 @@ object(Dom\DocumentType)#3 (19) { NULL ["isConnected"]=> bool(false) + ["ownerDocument"]=> + NULL ["parentNode"]=> NULL ["parentElement"]=> @@ -68,7 +70,7 @@ object(Dom\DocumentType)#3 (19) { -object(Dom\DocumentType)#2 (19) { +object(Dom\DocumentType)#2 (20) { ["name"]=> string(5) "qname" ["entities"]=> @@ -89,6 +91,8 @@ object(Dom\DocumentType)#2 (19) { NULL ["isConnected"]=> bool(false) + ["ownerDocument"]=> + NULL ["parentNode"]=> NULL ["parentElement"]=> @@ -111,7 +115,7 @@ object(Dom\DocumentType)#2 (19) { -object(Dom\DocumentType)#1 (19) { +object(Dom\DocumentType)#1 (20) { ["name"]=> string(5) "qname" ["entities"]=> @@ -132,6 +136,8 @@ object(Dom\DocumentType)#1 (19) { NULL ["isConnected"]=> bool(false) + ["ownerDocument"]=> + NULL ["parentNode"]=> NULL ["parentElement"]=> @@ -154,7 +160,7 @@ object(Dom\DocumentType)#1 (19) { -object(Dom\DocumentType)#4 (19) { +object(Dom\DocumentType)#4 (20) { ["name"]=> string(5) "qname" ["entities"]=> @@ -175,6 +181,8 @@ object(Dom\DocumentType)#4 (19) { NULL ["isConnected"]=> bool(false) + ["ownerDocument"]=> + NULL ["parentNode"]=> NULL ["parentElement"]=> diff --git a/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt b/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt index fb0853939f88e..5b93e459b517e 100644 --- a/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt +++ b/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt @@ -142,7 +142,7 @@ object(Dom\Entity)#3 (17) { ["textContent"]=> NULL } -object(Dom\Notation)#4 (13) { +object(Dom\Notation)#4 (14) { ["nodeType"]=> int(12) ["nodeName"]=> @@ -151,6 +151,8 @@ object(Dom\Notation)#4 (13) { NULL ["isConnected"]=> bool(false) + ["ownerDocument"]=> + NULL ["parentNode"]=> NULL ["parentElement"]=> diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 86b8d29209f40..e636e7d8183f9 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -2068,7 +2068,7 @@ static HashTable *zend_ffi_cdata_get_debug_info(zend_object *obj, int *is_temp) switch (type->kind) { case ZEND_FFI_TYPE_VOID: - return NULL; + return (HashTable*)&zend_empty_array; case ZEND_FFI_TYPE_BOOL: case ZEND_FFI_TYPE_CHAR: case ZEND_FFI_TYPE_ENUM: @@ -2090,7 +2090,6 @@ static HashTable *zend_ffi_cdata_get_debug_info(zend_object *obj, int *is_temp) zend_hash_str_add(ht, "cdata", sizeof("cdata")-1, &tmp); *is_temp = 1; return ht; - break; case ZEND_FFI_TYPE_POINTER: if (*(void**)ptr == NULL) { ZVAL_NULL(&tmp); @@ -2145,12 +2144,10 @@ static HashTable *zend_ffi_cdata_get_debug_info(zend_object *obj, int *is_temp) // TODO: function name ??? *is_temp = 1; return ht; - break; default: ZEND_UNREACHABLE(); - break; + return NULL; } - return NULL; } /* }}} */ @@ -2299,7 +2296,7 @@ static int zend_ffi_ctype_compare_objects(zval *o1, zval *o2) /* {{{ */ static HashTable *zend_ffi_ctype_get_debug_info(zend_object *obj, int *is_temp) /* {{{ */ { - return NULL; + return (HashTable*)&zend_empty_array;; } /* }}} */ diff --git a/ext/ffi/tests/035.phpt b/ext/ffi/tests/035.phpt index bd8c6df71f97a..2b730cb016c1d 100644 --- a/ext/ffi/tests/035.phpt +++ b/ext/ffi/tests/035.phpt @@ -23,6 +23,4 @@ object(FFI\CData:uint16_t[2])#%d (2) { [1]=> int(0) } -object(FFI\CData:uint16_t[2])#%d (0) { -} FFI\Exception: Use after free() diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index a5e9ed166d8b3..37f380dba6abe 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -237,7 +237,7 @@ static HashTable* spl_fixedarray_object_get_properties_for(zend_object *obj, zen const zend_long size = intern->array.size; if (size == 0 && (!source_properties || !zend_hash_num_elements(source_properties))) { - return NULL; + return (HashTable*)&zend_empty_array; } zval *const elements = intern->array.elements; HashTable *ht = zend_new_array(size); diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index e0c4230dae27c..c9803c6f0db35 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -2079,7 +2079,11 @@ PHP_FUNCTION(print_r) ZEND_PARSE_PARAMETERS_END(); if (do_return) { - RETURN_STR(zend_print_zval_r_to_str(var, 0)); + zend_string *out = zend_print_zval_r_to_str(var, 0); + if (UNEXPECTED(out == NULL)) { + RETURN_THROWS(); + } + RETURN_STR(out); } else { zend_print_zval_r(var, 0); RETURN_TRUE; diff --git a/ext/standard/info.c b/ext/standard/info.c index 952f0f92fe6e2..656ea249a0fe3 100644 --- a/ext/standard/info.c +++ b/ext/standard/info.c @@ -204,6 +204,7 @@ static ZEND_COLD void php_print_gpcse_array(char *name, size_t name_length) if (Z_TYPE_P(tmp) == IS_ARRAY) { if (!sapi_module.phpinfo_as_text) { zend_string *str = zend_print_zval_r_to_str(tmp, 0); + ZEND_ASSERT(str != NULL && "shouldn't be possible to have a debug handler triggering an exception"); php_info_print("
");
 					php_info_print_html_esc(ZSTR_VAL(str), ZSTR_LEN(str));
 					php_info_print("
"); diff --git a/ext/standard/var.c b/ext/standard/var.c index a1ef60410a338..6730d2ddd6035 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -37,58 +37,111 @@ struct php_serialize_data { uint32_t n; }; -#define COMMON (is_ref ? "&" : "") +typedef bool (*php_dump_fn_t)(smart_str *buf, zval *struc, int level); -static void php_array_element_dump(zval *zv, zend_ulong index, zend_string *key, int level) /* {{{ */ -{ +static void php_smart_str_indent(smart_str *buf, int level) { + for (int i = 0; i < level; i++) { + smart_str_appendc(buf, ' '); + } +} + +static bool php_array_element_dump( + smart_str *buf, + zval *zv, zend_ulong index, + const zend_string *key, + int level, + php_dump_fn_t continuation +) { + php_smart_str_indent(buf, level); + smart_str_appendc(buf, '['); if (key == NULL) { /* numeric key */ - php_printf("%*c[" ZEND_LONG_FMT "]=>\n", level + 1, ' ', index); + smart_str_append_long(buf, (zend_long) index); } else { /* string key */ - php_printf("%*c[\"", level + 1, ' '); - PHPWRITE(ZSTR_VAL(key), ZSTR_LEN(key)); - php_printf("\"]=>\n"); + smart_str_appendc(buf, '"'); + smart_str_append(buf, key); + smart_str_appendc(buf, '"'); } - php_var_dump(zv, level + 2); + smart_str_appends(buf, "]=>\n"); + return continuation(buf, zv, level); } -/* }}} */ -static void php_object_property_dump(zend_property_info *prop_info, zval *zv, zend_ulong index, zend_string *key, int level) /* {{{ */ -{ +static bool php_dump_array_common( + smart_str *buf, + HashTable *ht, + int level, + php_dump_fn_t continuation +) { + bool status = true; + zend_ulong index; + zend_string *key; + zval *val; + ZEND_HASH_FOREACH_KEY_VAL(ht, index, key, val) { + status = php_array_element_dump(buf, val, index, key, level + 2, continuation); + /* An exception occurred in a debug handler */ + if (UNEXPECTED(!status)) { + break; + } + } ZEND_HASH_FOREACH_END(); + if (!(GC_FLAGS(ht) & GC_IMMUTABLE)) { + GC_UNPROTECT_RECURSION(ht); + GC_DTOR_NO_REF(ht); + } + php_smart_str_indent(buf, level); + smart_str_appends(buf, "}\n"); + return status; +} + +static bool php_object_property_dump( + smart_str *buf, + const zend_property_info *prop_info, + zval *zv, zend_ulong index, + const zend_string *key, + int level, + php_dump_fn_t continuation +) { const char *prop_name, *class_name; + php_smart_str_indent(buf, level); + smart_str_appendc(buf, '['); if (key == NULL) { /* numeric key */ - php_printf("%*c[" ZEND_LONG_FMT "]=>\n", level + 1, ' ', index); + smart_str_append_long(buf, (zend_long) index); } else { /* string key */ - int unmangle = zend_unmangle_property_name(key, &class_name, &prop_name); - php_printf("%*c[", level + 1, ' '); + zend_result unmangle = zend_unmangle_property_name(key, &class_name, &prop_name); + smart_str_appendc(buf, '"'); if (class_name && unmangle == SUCCESS) { + smart_str_appends(buf, prop_name); + smart_str_appendc(buf, '"'); + smart_str_appendc(buf, ':'); if (class_name[0] == '*') { - php_printf("\"%s\":protected", prop_name); + smart_str_appends(buf, "protected"); } else { - php_printf("\"%s\":\"%s\":private", prop_name, class_name); + smart_str_appendc(buf, '"'); + smart_str_appends(buf, class_name); + smart_str_appends(buf, "\":private"); } } else { - php_printf("\""); - PHPWRITE(ZSTR_VAL(key), ZSTR_LEN(key)); - php_printf("\""); + smart_str_append(buf, key); + smart_str_appendc(buf, '"'); } - ZEND_PUTS("]=>\n"); } + smart_str_appends(buf, "]=>\n"); if (Z_TYPE_P(zv) == IS_UNDEF) { ZEND_ASSERT(ZEND_TYPE_IS_SET(prop_info->type)); zend_string *type_str = zend_type_to_string(prop_info->type); - php_printf("%*cuninitialized(%s)\n", - level + 1, ' ', ZSTR_VAL(type_str)); + php_smart_str_indent(buf, level); + smart_str_appends(buf, "uninitialized("); + smart_str_append(buf, type_str); + smart_str_appends(buf, ")\n"); zend_string_release(type_str); + return true; } else { - php_var_dump(zv, level + 2); + return continuation(buf, zv, level); } } -/* }}} */ -static const char *php_var_dump_object_prefix(zend_object *obj) { +static const char *php_var_dump_object_prefix(const zend_object *obj) { if (EXPECTED(!zend_object_is_lazy(obj))) { return ""; } @@ -100,343 +153,325 @@ static const char *php_var_dump_object_prefix(zend_object *obj) { return "lazy ghost "; } -PHPAPI void php_var_dump(zval *struc, int level) /* {{{ */ -{ - HashTable *myht; - zend_string *class_name; - int is_ref = 0; +static bool php_var_dump_ex(smart_str *buf, zval *struc, int level); +static bool php_debug_zval_dump_ex(smart_str *buf, zval *struc, int level); + +static bool php_dump_object_common( + smart_str *buf, + zval *object, + int level, + bool is_ref, + php_dump_fn_t continuation +) { + const zend_class_entry *ce = Z_OBJCE_P(object); + // TODO: Handle enums separately for debug_zval_dump() like in var_dump() and print_r()? + if (continuation == php_var_dump_ex && ce->ce_flags & ZEND_ACC_ENUM) { + const zval *case_name_zval = zend_enum_fetch_case_name(Z_OBJ_P(object)); + + if (is_ref) { smart_str_appendc(buf, '&'); } + smart_str_appends(buf, "enum("); + smart_str_append(buf, ce->name); + smart_str_appends(buf, "::"); + smart_str_append(buf, Z_STR_P(case_name_zval)); + smart_str_appends(buf, ")\n"); + return true; + } + /* Check if this is already recursing on the object before calling zend_get_properties_for, + * to allow infinite recursion detection to work even if classes return temporary arrays, + * and to avoid the need to update the properties table in place to reflect the state + * if the result won't be used. (https://github.com/php/php-src/issues/8044) */ + zend_object *zobj = Z_OBJ_P(object); + uint32_t *guard = zend_get_recursion_guard(zobj); + if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) { + smart_str_appends(buf, "*RECURSION*\n"); + return true; + } + ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj); + + HashTable *myht = zend_get_properties_for(object, ZEND_PROP_PURPOSE_DEBUG); + /* Either we must have properties as a HashTable (even if empty) or an exception if NULL is returned */ + ZEND_ASSERT(myht || EG(exception)); + if (UNEXPECTED(myht == NULL)) { + ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj); + return false; + } + + zend_string *class_name = Z_OBJ_HANDLER_P(object, get_class_name)(Z_OBJ_P(object)); + const char *prefix = php_var_dump_object_prefix(Z_OBJ_P(object)); + + if (is_ref) { smart_str_appendc(buf, '&'); } + smart_str_appends(buf, prefix); + smart_str_appends(buf, "object("); + /* We use smart_str_appends() to be able to truncate anonymous class names which are separated with a nul byte */ + smart_str_appends(buf, ZSTR_VAL(class_name)); + zend_string_release_ex(class_name, false); + + smart_str_appends(buf, ")#"); + smart_str_append_unsigned(buf, Z_OBJ_HANDLE_P(object)); + smart_str_appends(buf, " ("); + smart_str_append_unsigned(buf, zend_array_count(myht)); + + /* Need to specify refcount for debug_zval_dump() */ + if (continuation == php_debug_zval_dump_ex) { + smart_str_appends(buf, ") refcount("); + smart_str_append_unsigned(buf, Z_REFCOUNT_P(object)); + smart_str_appends(buf, "){\n"); + } else { + smart_str_appends(buf, ") {\n"); + } + zend_ulong num; zend_string *key; zval *val; - uint32_t count; - if (level > 1) { - php_printf("%*c", level - 1, ' '); + bool status = true; + ZEND_HASH_FOREACH_KEY_VAL(myht, num, key, val) { + const zend_property_info *prop_info = NULL; + + if (Z_TYPE_P(val) == IS_INDIRECT) { + val = Z_INDIRECT_P(val); + if (key) { + prop_info = zend_get_typed_property_info_for_slot(Z_OBJ_P(object), val); + } + } + + if (!Z_ISUNDEF_P(val) || prop_info) { + status = php_object_property_dump(buf, prop_info, val, num, key, level + 2, continuation); + if (UNEXPECTED(!status)) { + break; + } + } + } ZEND_HASH_FOREACH_END(); + zend_array_release(myht); + + php_smart_str_indent(buf, level); + smart_str_appends(buf, "}\n"); + ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj); + return status; +} + +static void php_var_dump_common(smart_str *buf, const zval *struc) +{ + switch (Z_TYPE_P(struc)) { + case IS_FALSE: + smart_str_appends(buf, "bool(false)\n"); + return; + case IS_TRUE: + smart_str_appends(buf, "bool(true)\n"); + return; + case IS_NULL: + smart_str_appends(buf, "NULL\n"); + return; + case IS_LONG: + smart_str_appends(buf, "int("); + smart_str_append_long(buf, Z_LVAL_P(struc)); + smart_str_appends(buf, ")\n"); + return; + case IS_DOUBLE: + smart_str_appends(buf, "float("); + smart_str_append_double(buf, Z_DVAL_P(struc), (int) PG(serialize_precision), false); + smart_str_appends(buf, ")\n"); + return; + case IS_RESOURCE: { + const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(struc)); + smart_str_appends(buf, "resource("); + smart_str_append_long(buf, Z_RES_P(struc)->handle); + smart_str_appends(buf, ") of type ("); + smart_str_appends(buf, type_name ? type_name : "Unknown"); + smart_str_appendc(buf, ')'); + return; + } + case IS_STRING: + smart_str_appends(buf, "string("); + smart_str_append_unsigned(buf, Z_STRLEN_P(struc)); + smart_str_appends(buf, ") \""); + smart_str_append(buf, Z_STR_P(struc)); + smart_str_appendc(buf, '"'); + return; + default: + smart_str_appends(buf, "UNKNOWN:0\n"); + return; } +} + +static bool php_var_dump_ex(smart_str *buf, zval *struc, int level) /* {{{ */ +{ + bool is_ref = false; + + php_smart_str_indent(buf, level); again: switch (Z_TYPE_P(struc)) { case IS_FALSE: - php_printf("%sbool(false)\n", COMMON); - break; case IS_TRUE: - php_printf("%sbool(true)\n", COMMON); - break; case IS_NULL: - php_printf("%sNULL\n", COMMON); - break; case IS_LONG: - php_printf("%sint(" ZEND_LONG_FMT ")\n", COMMON, Z_LVAL_P(struc)); - break; case IS_DOUBLE: - php_printf_unchecked("%sfloat(%.*H)\n", COMMON, (int) PG(serialize_precision), Z_DVAL_P(struc)); - break; + default: + if (is_ref) { smart_str_appendc(buf, '&'); } + php_var_dump_common(buf, struc); + return true; case IS_STRING: - php_printf("%sstring(%zd) \"", COMMON, Z_STRLEN_P(struc)); - PHPWRITE(Z_STRVAL_P(struc), Z_STRLEN_P(struc)); - PUTS("\"\n"); - break; - case IS_ARRAY: - myht = Z_ARRVAL_P(struc); + case IS_RESOURCE: { + if (is_ref) { smart_str_appendc(buf, '&'); } + php_var_dump_common(buf, struc); + smart_str_appendc(buf, '\n'); + return true; + } + case IS_ARRAY: { + HashTable *myht = Z_ARRVAL_P(struc); if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) { if (GC_IS_RECURSIVE(myht)) { - PUTS("*RECURSION*\n"); - return; + smart_str_appends(buf, "*RECURSION*\n"); + return true; } GC_ADDREF(myht); GC_PROTECT_RECURSION(myht); } - count = zend_hash_num_elements(myht); - php_printf("%sarray(%d) {\n", COMMON, count); - ZEND_HASH_FOREACH_KEY_VAL(myht, num, key, val) { - php_array_element_dump(val, num, key, level); - } ZEND_HASH_FOREACH_END(); - if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) { - GC_UNPROTECT_RECURSION(myht); - GC_DTOR_NO_REF(myht); - } - if (level > 1) { - php_printf("%*c", level-1, ' '); - } - PUTS("}\n"); - break; - case IS_OBJECT: { - zend_class_entry *ce = Z_OBJCE_P(struc); - if (ce->ce_flags & ZEND_ACC_ENUM) { - zval *case_name_zval = zend_enum_fetch_case_name(Z_OBJ_P(struc)); - php_printf("%senum(%s::%s)\n", COMMON, ZSTR_VAL(ce->name), Z_STRVAL_P(case_name_zval)); - return; - } - zend_object *zobj = Z_OBJ_P(struc); - uint32_t *guard = zend_get_recursion_guard(zobj); - if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) { - PUTS("*RECURSION*\n"); - return; - } - ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj); - myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG); - class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc)); - const char *prefix = php_var_dump_object_prefix(Z_OBJ_P(struc)); + if (is_ref) { smart_str_appendc(buf, '&'); } + smart_str_appends(buf, "array("); + smart_str_append_unsigned(buf, zend_hash_num_elements(myht)); + smart_str_appends(buf, ") {\n"); - php_printf("%s%sobject(%s)#%d (%d) {\n", COMMON, prefix, ZSTR_VAL(class_name), Z_OBJ_HANDLE_P(struc), myht ? zend_array_count(myht) : 0); - zend_string_release_ex(class_name, 0); - - if (myht) { - zend_ulong num; - zend_string *key; - zval *val; - - ZEND_HASH_FOREACH_KEY_VAL(myht, num, key, val) { - zend_property_info *prop_info = NULL; - - if (Z_TYPE_P(val) == IS_INDIRECT) { - val = Z_INDIRECT_P(val); - if (key) { - prop_info = zend_get_typed_property_info_for_slot(Z_OBJ_P(struc), val); - } - } - - if (!Z_ISUNDEF_P(val) || prop_info) { - php_object_property_dump(prop_info, val, num, key, level); - } - } ZEND_HASH_FOREACH_END(); - zend_release_properties(myht); - } - if (level > 1) { - php_printf("%*c", level-1, ' '); - } - PUTS("}\n"); - ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj); - break; - } - case IS_RESOURCE: { - const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(struc)); - php_printf("%sresource(" ZEND_LONG_FMT ") of type (%s)\n", COMMON, Z_RES_P(struc)->handle, type_name ? type_name : "Unknown"); - break; + return php_dump_array_common(buf, myht, level, php_var_dump_ex); } + case IS_OBJECT: + return php_dump_object_common(buf, struc, level, is_ref, php_var_dump_ex); case IS_REFERENCE: //??? hide references with refcount==1 (for compatibility) if (Z_REFCOUNT_P(struc) > 1) { - is_ref = 1; + is_ref = true; } struc = Z_REFVAL_P(struc); goto again; - break; - default: - php_printf("%sUNKNOWN:0\n", COMMON); - break; } } /* }}} */ +PHPAPI void php_var_dump(zval *struc, int level) +{ + smart_str buf = {0}; + bool status = php_var_dump_ex(&buf, struc, level); + if (UNEXPECTED(!status)) { + smart_str_free(&buf); + return; + } + smart_str_0(&buf); + PHPWRITE(ZSTR_VAL(buf.s), ZSTR_LEN(buf.s)); + smart_str_free(&buf); +} + /* {{{ Dumps a string representation of variable to output */ PHP_FUNCTION(var_dump) { zval *args; - int argc; - int i; + uint32_t argc; ZEND_PARSE_PARAMETERS_START(1, -1) Z_PARAM_VARIADIC('+', args, argc) ZEND_PARSE_PARAMETERS_END(); - for (i = 0; i < argc; i++) { - php_var_dump(&args[i], 1); + for (uint32_t i = 0; i < argc; i++) { + php_var_dump(&args[i], 0); } } /* }}} */ -static void zval_array_element_dump(zval *zv, zend_ulong index, zend_string *key, int level) /* {{{ */ +static bool php_debug_zval_dump_ex(smart_str *buf, zval *struc, int level) /* {{{ */ { - if (key == NULL) { /* numeric key */ - php_printf("%*c[" ZEND_LONG_FMT "]=>\n", level + 1, ' ', index); - } else { /* string key */ - php_printf("%*c[\"", level + 1, ' '); - PHPWRITE(ZSTR_VAL(key), ZSTR_LEN(key)); - php_printf("\"]=>\n"); - } - php_debug_zval_dump(zv, level + 2); -} -/* }}} */ + php_smart_str_indent(buf, level); -static void zval_object_property_dump(zend_property_info *prop_info, zval *zv, zend_ulong index, zend_string *key, int level) /* {{{ */ -{ - const char *prop_name, *class_name; - - if (key == NULL) { /* numeric key */ - php_printf("%*c[" ZEND_LONG_FMT "]=>\n", level + 1, ' ', index); - } else { /* string key */ - zend_unmangle_property_name(key, &class_name, &prop_name); - php_printf("%*c[", level + 1, ' '); - - if (class_name) { - if (class_name[0] == '*') { - php_printf("\"%s\":protected", prop_name); + switch (Z_TYPE_P(struc)) { + case IS_FALSE: + case IS_TRUE: + case IS_NULL: + case IS_LONG: + case IS_DOUBLE: + default: + php_var_dump_common(buf, struc); + return true; + case IS_STRING: + case IS_RESOURCE: { + php_var_dump_common(buf, struc); + if (Z_REFCOUNTED_P(struc)) { + smart_str_appends(buf, " refcount("); + smart_str_append_unsigned(buf, Z_REFCOUNT_P(struc)); + smart_str_appends(buf, ")\n"); } else { - php_printf("\"%s\":\"%s\":private", prop_name, class_name); + smart_str_appends(buf, " interned\n"); } - } else { - php_printf("\"%s\"", prop_name); + return true; } - ZEND_PUTS("]=>\n"); - } - if (prop_info && Z_TYPE_P(zv) == IS_UNDEF) { - zend_string *type_str = zend_type_to_string(prop_info->type); - php_printf("%*cuninitialized(%s)\n", - level + 1, ' ', ZSTR_VAL(type_str)); - zend_string_release(type_str); - } else { - php_debug_zval_dump(zv, level + 2); - } -} -/* }}} */ - -PHPAPI void php_debug_zval_dump(zval *struc, int level) /* {{{ */ -{ - HashTable *myht = NULL; - zend_string *class_name; - zend_ulong index; - zend_string *key; - zval *val; - uint32_t count; - char *packed; - - if (level > 1) { - php_printf("%*c", level - 1, ' '); - } - - switch (Z_TYPE_P(struc)) { - case IS_FALSE: - PUTS("bool(false)\n"); - break; - case IS_TRUE: - PUTS("bool(true)\n"); - break; - case IS_NULL: - PUTS("NULL\n"); - break; - case IS_LONG: - php_printf("int(" ZEND_LONG_FMT ")\n", Z_LVAL_P(struc)); - break; - case IS_DOUBLE: - php_printf_unchecked("float(%.*H)\n", (int) PG(serialize_precision), Z_DVAL_P(struc)); - break; - case IS_STRING: - php_printf("string(%zd) \"", Z_STRLEN_P(struc)); - PHPWRITE(Z_STRVAL_P(struc), Z_STRLEN_P(struc)); - if (Z_REFCOUNTED_P(struc)) { - php_printf("\" refcount(%u)\n", Z_REFCOUNT_P(struc)); - } else { - PUTS("\" interned\n"); - } - break; - case IS_ARRAY: - myht = Z_ARRVAL_P(struc); - if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) { - if (GC_IS_RECURSIVE(myht)) { - PUTS("*RECURSION*\n"); - return; + case IS_ARRAY: { + HashTable *myht = Z_ARRVAL_P(struc); + if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) { + if (GC_IS_RECURSIVE(myht)) { + smart_str_appends(buf, "*RECURSION*\n"); + return true; + } + GC_ADDREF(myht); + GC_PROTECT_RECURSION(myht); } - GC_ADDREF(myht); - GC_PROTECT_RECURSION(myht); - } - count = zend_hash_num_elements(myht); - packed = HT_IS_PACKED(myht) ? "packed " : ""; - if (Z_REFCOUNTED_P(struc)) { - /* -1 because of ADDREF above. */ - php_printf("array(%d) %srefcount(%u){\n", count, packed, Z_REFCOUNT_P(struc) - 1); - } else { - php_printf("array(%d) %sinterned {\n", count, packed); - } - ZEND_HASH_FOREACH_KEY_VAL(myht, index, key, val) { - zval_array_element_dump(val, index, key, level); - } ZEND_HASH_FOREACH_END(); - if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) { - GC_UNPROTECT_RECURSION(myht); - GC_DTOR_NO_REF(myht); - } - if (level > 1) { - php_printf("%*c", level - 1, ' '); - } - PUTS("}\n"); - break; - case IS_OBJECT: { - /* Check if this is already recursing on the object before calling zend_get_properties_for, - * to allow infinite recursion detection to work even if classes return temporary arrays, - * and to avoid the need to update the properties table in place to reflect the state - * if the result won't be used. (https://github.com/php/php-src/issues/8044) */ - zend_object *zobj = Z_OBJ_P(struc); - uint32_t *guard = zend_get_recursion_guard(zobj); - if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) { - PUTS("*RECURSION*\n"); - return; - } - ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj); - - myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG); - class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc)); - const char *prefix = php_var_dump_object_prefix(Z_OBJ_P(struc)); - - php_printf("%sobject(%s)#%d (%d) refcount(%u){\n", prefix, ZSTR_VAL(class_name), Z_OBJ_HANDLE_P(struc), myht ? zend_array_count(myht) : 0, Z_REFCOUNT_P(struc)); - zend_string_release_ex(class_name, 0); - if (myht) { - ZEND_HASH_FOREACH_KEY_VAL(myht, index, key, val) { - zend_property_info *prop_info = NULL; - if (Z_TYPE_P(val) == IS_INDIRECT) { - val = Z_INDIRECT_P(val); - if (key) { - prop_info = zend_get_typed_property_info_for_slot(Z_OBJ_P(struc), val); - } - } + smart_str_appends(buf, "array("); + smart_str_append_unsigned(buf, zend_hash_num_elements(myht)); + smart_str_appendc(buf, ')'); + if (HT_IS_PACKED(myht)) { + smart_str_appends(buf, " packed"); + } + if (Z_REFCOUNTED_P(struc)) { + smart_str_appends(buf, " refcount("); + /* -1 because of ADDREF above. */ + smart_str_append_unsigned(buf, Z_REFCOUNT_P(struc) - 1); + smart_str_appends(buf, ")"); + } else { + smart_str_appends(buf, " interned "); + } + smart_str_appends(buf, "{\n"); - if (!Z_ISUNDEF_P(val) || prop_info) { - zval_object_property_dump(prop_info, val, index, key, level); - } - } ZEND_HASH_FOREACH_END(); - zend_release_properties(myht); + return php_dump_array_common(buf, myht, level, php_debug_zval_dump_ex); } - if (level > 1) { - php_printf("%*c", level - 1, ' '); + case IS_OBJECT: + return php_dump_object_common(buf, struc, level, false, php_debug_zval_dump_ex); + case IS_REFERENCE: { + smart_str_appends(buf, "reference refcount("); + smart_str_append_unsigned(buf, Z_REFCOUNT_P(struc)); + smart_str_appends(buf, ") {\n"); + bool status = php_debug_zval_dump_ex(buf, Z_REFVAL_P(struc), level + 2); + php_smart_str_indent(buf, level); + smart_str_appends(buf, "}\n"); + return status; } - PUTS("}\n"); - ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj); - break; - } - case IS_RESOURCE: { - const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(struc)); - php_printf("resource(" ZEND_LONG_FMT ") of type (%s) refcount(%u)\n", Z_RES_P(struc)->handle, type_name ? type_name : "Unknown", Z_REFCOUNT_P(struc)); - break; - } - case IS_REFERENCE: - php_printf("reference refcount(%u) {\n", Z_REFCOUNT_P(struc)); - php_debug_zval_dump(Z_REFVAL_P(struc), level + 2); - if (level > 1) { - php_printf("%*c", level - 1, ' '); - } - PUTS("}\n"); - break; - default: - PUTS("UNKNOWN:0\n"); - break; } } /* }}} */ +PHPAPI void php_debug_zval_dump(zval *struc, int level) +{ + smart_str buf = {0}; + bool status = php_debug_zval_dump_ex(&buf, struc, level); + if (UNEXPECTED(!status)) { + smart_str_free(&buf); + return; + } + smart_str_0(&buf); + PHPWRITE(ZSTR_VAL(buf.s), ZSTR_LEN(buf.s)); + smart_str_free(&buf); +} + /* {{{ Dumps a string representation of an internal zend value to output. */ PHP_FUNCTION(debug_zval_dump) { zval *args; - int argc; - int i; + uint32_t argc; ZEND_PARSE_PARAMETERS_START(1, -1) Z_PARAM_VARIADIC('+', args, argc) ZEND_PARSE_PARAMETERS_END(); - for (i = 0; i < argc; i++) { - php_debug_zval_dump(&args[i], 1); + for (uint32_t i = 0; i < argc; i++) { + php_debug_zval_dump(&args[i], 0); } } /* }}} */