Skip to content

Commit debc17c

Browse files
committed
gen_stub: Fix handling of escape sequences in generated C strings
When handling sequences like this in a stub: ```php <?php class Whatever { public static string $foobar1 = "CCC \n\r\t\v\e\f\\\$\"\101\x41\u{41} CCC"; public static string $foobar2 = 'CCC \n\r\t\v\e\f\\\$\"\101\x41\u{41} CCC'; } ``` ...properly generate C headers that properly escape the string. Otherwise, the differing escaping rules and differences between PHP's single and double quoted strings could lead to mangled headers. The output of these strings after the stub has been generated: ``` string(22) "BEGIN �� \$"AAA END" string(43) "BEGIN \n\r\t\v\e\f\\\\$\"\101\x41\u{41} END" ``` And the generated arginfo: ```c 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); pp 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); ``` Note that the PHP escape sequence "\$" will be handled in a special manner to avoid providing it to C, to avoid C compiler warnings. Tests are included via the zend_test stub and a phpt file to ensure that the escape sequences match. Fixes GH-22169.
1 parent 5170c01 commit debc17c

6 files changed

Lines changed: 97 additions & 8 deletions

File tree

build/gen_stub.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2344,9 +2344,28 @@ public function getCExpr(): ?string
23442344
// $this->expr has all its PHP constants replaced by C constants
23452345
$prettyPrinter = new Standard;
23462346
$expr = $prettyPrinter->prettyPrintExpr($this->expr);
2347-
// PHP single-quote to C double-quote string
23482347
if ($this->type->isString()) {
2349-
$expr = preg_replace("/(^'|'$)/", '"', $expr);
2348+
// The string in $expr has had the octal, hex, and unicode
2349+
// backslash sequences already applied for double-quoted strings,
2350+
// but not the other sequences.
2351+
//
2352+
// PHP has single quote strings, C doesn't (they're one char).
2353+
// Single-quoted strings need handling to replace their escapes
2354+
// with the double-quoted equivalent; namely single quote escapes.
2355+
//
2356+
// Double-quoted strings have similar escape sequences as C does,
2357+
// so we can pass them through directly. However, C does *not*
2358+
// support the \$ escape sequence (in ""), so strip that. Variable
2359+
// interpolation shouldn't be possible in a stub, so we don't need
2360+
// to worry about mangling such a case.
2361+
if (preg_match("/(^'|'$)/", $expr)) {
2362+
$expr = substr($expr, 1, -1); // strip quotes, readd later
2363+
$expr = str_replace("\\'", "'", $expr);
2364+
$expr = addcslashes($expr, "\\\"");
2365+
$expr = "\"$expr\"";
2366+
} else {
2367+
$expr = str_replace('\$', "$", $expr);
2368+
}
23502369
}
23512370
return $expr[0] == '"' ? $expr : preg_replace('(\bnull\b)', 'NULL', str_replace('\\', '', $expr));
23522371
}

ext/zend_test/test.stub.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ class _ZendTestClass implements _ZendTestInterface {
5757
public static $_StaticProp;
5858
public static int $staticIntProp = 123;
5959

60+
/* If there's a problem with escapes in quotes in generated headers,
61+
* the generated header won't compile. (tests/gh22169.phpt) */
62+
public static string $doubleQuoteEscaped = "BEGIN \n\r\t\v\e\f\\\$\"\101\x41\u{41} END";
63+
public static string $singleQuoteEscaped = 'BEGIN \n\r\t\v\e\f\\\$\"\101\x41\u{41} END';
64+
public static string $escapeInterpolated = "begin \$ \\$ end";
65+
6066
public int $intProp = 123;
6167
public ?stdClass $classProp = null;
6268
public stdClass|Iterator|null $classUnionProp = null;

ext/zend_test/test_arginfo.h

Lines changed: 22 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/zend_test/test_decl.h

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/zend_test/test_legacy_arginfo.h

Lines changed: 22 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/zend_test/tests/gh22169.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
GH-22169: Ensure escaped strings in stubs are valid
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
// Avoid funny control characters in output...
9+
// "BEGIN " 424547494e20
10+
// "\n\r\t\v\e\f" 0a0d090b1b0c
11+
// "\\\$\"" 5c2422
12+
// "\101\x41\u{41}" 414141
13+
// " END" 20454e44
14+
var_dump(bin2hex(_ZendTestClass::$doubleQuoteEscaped));
15+
var_dump(_ZendTestClass::$singleQuoteEscaped);
16+
var_dump(_ZendTestClass::$escapeInterpolated);
17+
?>
18+
--EXPECT--
19+
string(44) "424547494e200a0d090b1b0c5c242241414120454e44"
20+
string(43) "BEGIN \n\r\t\v\e\f\\\\$\"\101\x41\u{41} END"
21+
string(14) "begin $ \$ end"
22+

0 commit comments

Comments
 (0)