From b1e2fe21626d7015017e13675d6ab875bfbd5b6a Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Wed, 10 Jun 2026 16:00:12 -0300 Subject: [PATCH 1/3] gen_stub: Fix handling of escape sequences in generated C strings When handling sequences like this in a stub: ```php expr has all its PHP constants replaced by C constants $prettyPrinter = new Standard; $expr = $prettyPrinter->prettyPrintExpr($this->expr); - // PHP single-quote to C double-quote string if ($this->type->isString()) { - $expr = preg_replace("/(^'|'$)/", '"', $expr); + // The string in $expr has had the octal, hex, and unicode + // backslash sequences already applied for double-quoted strings, + // but not the other sequences. + // PHP has single quote strings, C doesn't (they're one char). + // Single-quoted strings need handling to replace their escapes + // with the double-quoted equivalent; namely single quote escapes. + // Double-quoted strings have similar escape sequences as C does, + // so we can pass them through directly. + if (preg_match("/(^'|'$)/", $expr)) { + $expr = substr($expr, 1, -1); // strip quotes, readd later + $expr = str_replace("\\'", "'", $expr); + $expr = addcslashes($expr, "\\\""); + $expr = "\"$expr\""; + } } return $expr[0] == '"' ? $expr : preg_replace('(\bnull\b)', 'NULL', str_replace('\\', '', $expr)); } From bac65c964b39391da4208eeb4d4d9d37c4e29dd5 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Wed, 10 Jun 2026 18:35:50 -0300 Subject: [PATCH 2/3] gen_stub: Add some escaped strings in the zend_test stub --- ext/zend_test/test.stub.php | 7 +++++++ ext/zend_test/test_arginfo.h | 16 +++++++++++++++- ext/zend_test/test_decl.h | 8 ++++---- ext/zend_test/test_legacy_arginfo.h | 16 +++++++++++++++- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 489d7d0a260b..9f49f32876a2 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -9,6 +9,8 @@ namespace { require "Zend/zend_attributes.stub.php"; + + /** * @var int * @deprecated @@ -57,6 +59,11 @@ class _ZendTestClass implements _ZendTestInterface { public static $_StaticProp; public static int $staticIntProp = 123; + /* If there's a problem with escapes in quotes in generated headers, + * the generated header won't compile. */ + public static string $doubleQuoteEscaped = "BEGIN \n\r\t\v\e\f\\\$\"\101\x41\u{41} END"; + public static string $singleQuoteEscaped = 'BEGIN \n\r\t\v\e\f\\\$\"\101\x41\u{41} END'; + public int $intProp = 123; public ?stdClass $classProp = null; public stdClass|Iterator|null $classUnionProp = null; diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 94f75cdb3601..b76e54424a7d 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 4bb5b467b9d62c0e0c6a7c1e069e8755403a0af9 + * Stub hash: 3bc93319ae06a7f24bdc2dcce82346fb4b08329a * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) @@ -795,6 +795,20 @@ static zend_class_entry *register_class__ZendTestClass(zend_class_entry *class_e zend_declare_typed_property(class_entry, property_staticIntProp_name, &property_staticIntProp_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release_ex(property_staticIntProp_name, true); + zval property_doubleQuoteEscaped_default_value; + zend_string *property_doubleQuoteEscaped_default_value_str = zend_string_init("BEGIN \n\r\t\v\x1b\f\\\$\"AAA END", strlen("BEGIN \n\r\t\v\x1b\f\\\$\"AAA END"), 1); + ZVAL_STR(&property_doubleQuoteEscaped_default_value, property_doubleQuoteEscaped_default_value_str); + zend_string *property_doubleQuoteEscaped_name = zend_string_init("doubleQuoteEscaped", sizeof("doubleQuoteEscaped") - 1, true); + zend_declare_typed_property(class_entry, property_doubleQuoteEscaped_name, &property_doubleQuoteEscaped_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release_ex(property_doubleQuoteEscaped_name, true); + + zval property_singleQuoteEscaped_default_value; + zend_string *property_singleQuoteEscaped_default_value_str = zend_string_init("BEGIN \\n\\r\\t\\v\\e\\f\\\\\\\\$\\\"\\101\\x41\\u{41} END", strlen("BEGIN \\n\\r\\t\\v\\e\\f\\\\\\\\$\\\"\\101\\x41\\u{41} END"), 1); + ZVAL_STR(&property_singleQuoteEscaped_default_value, property_singleQuoteEscaped_default_value_str); + zend_string *property_singleQuoteEscaped_name = zend_string_init("singleQuoteEscaped", sizeof("singleQuoteEscaped") - 1, true); + zend_declare_typed_property(class_entry, property_singleQuoteEscaped_name, &property_singleQuoteEscaped_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release_ex(property_singleQuoteEscaped_name, true); + zval property_intProp_default_value; ZVAL_LONG(&property_intProp_default_value, 123); zend_string *property_intProp_name = zend_string_init("intProp", sizeof("intProp") - 1, true); diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h index 4a6babbe12b9..954fd3020efa 100644 --- a/ext/zend_test/test_decl.h +++ b/ext/zend_test/test_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 4bb5b467b9d62c0e0c6a7c1e069e8755403a0af9 */ + * Stub hash: 3bc93319ae06a7f24bdc2dcce82346fb4b08329a */ -#ifndef ZEND_TEST_DECL_4bb5b467b9d62c0e0c6a7c1e069e8755403a0af9_H -#define ZEND_TEST_DECL_4bb5b467b9d62c0e0c6a7c1e069e8755403a0af9_H +#ifndef ZEND_TEST_DECL_3bc93319ae06a7f24bdc2dcce82346fb4b08329a_H +#define ZEND_TEST_DECL_3bc93319ae06a7f24bdc2dcce82346fb4b08329a_H typedef enum zend_enum_ZendTestUnitEnum { ZEND_ENUM_ZendTestUnitEnum_Foo = 1, @@ -27,4 +27,4 @@ typedef enum zend_enum_ZendTestEnumWithInterface { ZEND_ENUM_ZendTestEnumWithInterface_Bar = 2, } zend_enum_ZendTestEnumWithInterface; -#endif /* ZEND_TEST_DECL_4bb5b467b9d62c0e0c6a7c1e069e8755403a0af9_H */ +#endif /* ZEND_TEST_DECL_3bc93319ae06a7f24bdc2dcce82346fb4b08329a_H */ diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h index a4c1ae3f2c96..141e0f35406a 100644 --- a/ext/zend_test/test_legacy_arginfo.h +++ b/ext/zend_test/test_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 4bb5b467b9d62c0e0c6a7c1e069e8755403a0af9 + * Stub hash: 3bc93319ae06a7f24bdc2dcce82346fb4b08329a * Has decl header: yes */ ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, 0) @@ -650,6 +650,20 @@ static zend_class_entry *register_class__ZendTestClass(zend_class_entry *class_e zend_declare_property_ex(class_entry, property_staticIntProp_name, &property_staticIntProp_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL); zend_string_release_ex(property_staticIntProp_name, true); + zval property_doubleQuoteEscaped_default_value; + zend_string *property_doubleQuoteEscaped_default_value_str = zend_string_init("BEGIN \n\r\t\v\x1b\f\\\$\"AAA END", strlen("BEGIN \n\r\t\v\x1b\f\\\$\"AAA END"), 1); + ZVAL_STR(&property_doubleQuoteEscaped_default_value, property_doubleQuoteEscaped_default_value_str); + zend_string *property_doubleQuoteEscaped_name = zend_string_init("doubleQuoteEscaped", sizeof("doubleQuoteEscaped") - 1, true); + zend_declare_property_ex(class_entry, property_doubleQuoteEscaped_name, &property_doubleQuoteEscaped_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL); + zend_string_release_ex(property_doubleQuoteEscaped_name, true); + + zval property_singleQuoteEscaped_default_value; + zend_string *property_singleQuoteEscaped_default_value_str = zend_string_init("BEGIN \\n\\r\\t\\v\\e\\f\\\\\\\\$\\\"\\101\\x41\\u{41} END", strlen("BEGIN \\n\\r\\t\\v\\e\\f\\\\\\\\$\\\"\\101\\x41\\u{41} END"), 1); + ZVAL_STR(&property_singleQuoteEscaped_default_value, property_singleQuoteEscaped_default_value_str); + zend_string *property_singleQuoteEscaped_name = zend_string_init("singleQuoteEscaped", sizeof("singleQuoteEscaped") - 1, true); + zend_declare_property_ex(class_entry, property_singleQuoteEscaped_name, &property_singleQuoteEscaped_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL); + zend_string_release_ex(property_singleQuoteEscaped_name, true); + zval property_intProp_default_value; ZVAL_LONG(&property_intProp_default_value, 123); zend_string *property_intProp_name = zend_string_init("intProp", sizeof("intProp") - 1, true); From 553013958a7d8f9a976bf9a7d747838b0e739b20 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Wed, 10 Jun 2026 19:07:35 -0300 Subject: [PATCH 3/3] gen_stub: strip dollar sign escape sequence C doesn't have this, and will throw the following warning/error: ``` error: unknown escape sequence '\$' [-Werror,-Wunknown-escape-sequence] ``` Since adding this to zend_test for CI's sake, this will bomb out. Strip this specific sequence out to make cc happy. --- build/gen_stub.php | 9 ++++++++- ext/zend_test/test.stub.php | 1 + ext/zend_test/test_arginfo.h | 11 +++++++++-- ext/zend_test/test_decl.h | 8 ++++---- ext/zend_test/test_legacy_arginfo.h | 11 +++++++++-- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 2d856f07479a..e60e61885206 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -2348,16 +2348,23 @@ public function getCExpr(): ?string // The string in $expr has had the octal, hex, and unicode // backslash sequences already applied for double-quoted strings, // but not the other sequences. + // // PHP has single quote strings, C doesn't (they're one char). // Single-quoted strings need handling to replace their escapes // with the double-quoted equivalent; namely single quote escapes. + // // Double-quoted strings have similar escape sequences as C does, - // so we can pass them through directly. + // so we can pass them through directly. However, C does *not* + // support the \$ escape sequence (in ""), so strip that. Variable + // interpolation shouldn't be possible in a stub, so we don't need + // to worry about mangling such a case. if (preg_match("/(^'|'$)/", $expr)) { $expr = substr($expr, 1, -1); // strip quotes, readd later $expr = str_replace("\\'", "'", $expr); $expr = addcslashes($expr, "\\\""); $expr = "\"$expr\""; + } else { + $expr = str_replace('\$', "$", $expr); } } return $expr[0] == '"' ? $expr : preg_replace('(\bnull\b)', 'NULL', str_replace('\\', '', $expr)); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 9f49f32876a2..87009eedaa79 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -63,6 +63,7 @@ class _ZendTestClass implements _ZendTestInterface { * the generated header won't compile. */ public static string $doubleQuoteEscaped = "BEGIN \n\r\t\v\e\f\\\$\"\101\x41\u{41} END"; public static string $singleQuoteEscaped = 'BEGIN \n\r\t\v\e\f\\\$\"\101\x41\u{41} END'; + public static string $escapeInterpolated = "begin \$ \\$ end"; public int $intProp = 123; public ?stdClass $classProp = null; diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index b76e54424a7d..1c7e584c1b8e 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 3bc93319ae06a7f24bdc2dcce82346fb4b08329a + * Stub hash: cf25d2f3a90aec7e33d9ae681724aea27b93035f * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) @@ -796,7 +796,7 @@ static zend_class_entry *register_class__ZendTestClass(zend_class_entry *class_e zend_string_release_ex(property_staticIntProp_name, true); zval property_doubleQuoteEscaped_default_value; - zend_string *property_doubleQuoteEscaped_default_value_str = zend_string_init("BEGIN \n\r\t\v\x1b\f\\\$\"AAA END", strlen("BEGIN \n\r\t\v\x1b\f\\\$\"AAA END"), 1); + zend_string *property_doubleQuoteEscaped_default_value_str = zend_string_init("BEGIN \n\r\t\v\x1b\f\\$\"AAA END", strlen("BEGIN \n\r\t\v\x1b\f\\$\"AAA END"), 1); ZVAL_STR(&property_doubleQuoteEscaped_default_value, property_doubleQuoteEscaped_default_value_str); zend_string *property_doubleQuoteEscaped_name = zend_string_init("doubleQuoteEscaped", sizeof("doubleQuoteEscaped") - 1, true); zend_declare_typed_property(class_entry, property_doubleQuoteEscaped_name, &property_doubleQuoteEscaped_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); @@ -809,6 +809,13 @@ static zend_class_entry *register_class__ZendTestClass(zend_class_entry *class_e zend_declare_typed_property(class_entry, property_singleQuoteEscaped_name, &property_singleQuoteEscaped_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release_ex(property_singleQuoteEscaped_name, true); + zval property_escapeInterpolated_default_value; + zend_string *property_escapeInterpolated_default_value_str = zend_string_init("begin $ \\$ end", strlen("begin $ \\$ end"), 1); + ZVAL_STR(&property_escapeInterpolated_default_value, property_escapeInterpolated_default_value_str); + zend_string *property_escapeInterpolated_name = zend_string_init("escapeInterpolated", sizeof("escapeInterpolated") - 1, true); + zend_declare_typed_property(class_entry, property_escapeInterpolated_name, &property_escapeInterpolated_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release_ex(property_escapeInterpolated_name, true); + zval property_intProp_default_value; ZVAL_LONG(&property_intProp_default_value, 123); zend_string *property_intProp_name = zend_string_init("intProp", sizeof("intProp") - 1, true); diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h index 954fd3020efa..d2eae5696696 100644 --- a/ext/zend_test/test_decl.h +++ b/ext/zend_test/test_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 3bc93319ae06a7f24bdc2dcce82346fb4b08329a */ + * Stub hash: cf25d2f3a90aec7e33d9ae681724aea27b93035f */ -#ifndef ZEND_TEST_DECL_3bc93319ae06a7f24bdc2dcce82346fb4b08329a_H -#define ZEND_TEST_DECL_3bc93319ae06a7f24bdc2dcce82346fb4b08329a_H +#ifndef ZEND_TEST_DECL_cf25d2f3a90aec7e33d9ae681724aea27b93035f_H +#define ZEND_TEST_DECL_cf25d2f3a90aec7e33d9ae681724aea27b93035f_H typedef enum zend_enum_ZendTestUnitEnum { ZEND_ENUM_ZendTestUnitEnum_Foo = 1, @@ -27,4 +27,4 @@ typedef enum zend_enum_ZendTestEnumWithInterface { ZEND_ENUM_ZendTestEnumWithInterface_Bar = 2, } zend_enum_ZendTestEnumWithInterface; -#endif /* ZEND_TEST_DECL_3bc93319ae06a7f24bdc2dcce82346fb4b08329a_H */ +#endif /* ZEND_TEST_DECL_cf25d2f3a90aec7e33d9ae681724aea27b93035f_H */ diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h index 141e0f35406a..6ad7db97e1dc 100644 --- a/ext/zend_test/test_legacy_arginfo.h +++ b/ext/zend_test/test_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 3bc93319ae06a7f24bdc2dcce82346fb4b08329a + * Stub hash: cf25d2f3a90aec7e33d9ae681724aea27b93035f * Has decl header: yes */ ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, 0) @@ -651,7 +651,7 @@ static zend_class_entry *register_class__ZendTestClass(zend_class_entry *class_e zend_string_release_ex(property_staticIntProp_name, true); zval property_doubleQuoteEscaped_default_value; - zend_string *property_doubleQuoteEscaped_default_value_str = zend_string_init("BEGIN \n\r\t\v\x1b\f\\\$\"AAA END", strlen("BEGIN \n\r\t\v\x1b\f\\\$\"AAA END"), 1); + zend_string *property_doubleQuoteEscaped_default_value_str = zend_string_init("BEGIN \n\r\t\v\x1b\f\\$\"AAA END", strlen("BEGIN \n\r\t\v\x1b\f\\$\"AAA END"), 1); ZVAL_STR(&property_doubleQuoteEscaped_default_value, property_doubleQuoteEscaped_default_value_str); zend_string *property_doubleQuoteEscaped_name = zend_string_init("doubleQuoteEscaped", sizeof("doubleQuoteEscaped") - 1, true); zend_declare_property_ex(class_entry, property_doubleQuoteEscaped_name, &property_doubleQuoteEscaped_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL); @@ -664,6 +664,13 @@ static zend_class_entry *register_class__ZendTestClass(zend_class_entry *class_e zend_declare_property_ex(class_entry, property_singleQuoteEscaped_name, &property_singleQuoteEscaped_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL); zend_string_release_ex(property_singleQuoteEscaped_name, true); + zval property_escapeInterpolated_default_value; + zend_string *property_escapeInterpolated_default_value_str = zend_string_init("begin $ \\$ end", strlen("begin $ \\$ end"), 1); + ZVAL_STR(&property_escapeInterpolated_default_value, property_escapeInterpolated_default_value_str); + zend_string *property_escapeInterpolated_name = zend_string_init("escapeInterpolated", sizeof("escapeInterpolated") - 1, true); + zend_declare_property_ex(class_entry, property_escapeInterpolated_name, &property_escapeInterpolated_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL); + zend_string_release_ex(property_escapeInterpolated_name, true); + zval property_intProp_default_value; ZVAL_LONG(&property_intProp_default_value, 123); zend_string *property_intProp_name = zend_string_init("intProp", sizeof("intProp") - 1, true);