fix(stub): harden heredoc and nowdoc C++ escaping

pull/36/head
韩天峰 1 month ago
parent 2cbdbf9342
commit 3211be2833
  1. 38
      src/gen_stub.php
  2. 10
      tests/compiler/const/class-const-heredoc-nowdoc.phpt
  3. 21
      tests/compiler/const/heredoc-nowdoc-const-defaults.phpt

@ -834,7 +834,10 @@ class ArgInfo {
private function getDefaultValueAsArginfoString(): string {
if ($this->hasProperDefaultValue()) {
return '"' . addslashes($this->defaultValue) . '"';
// The default value is a PHP expression embedded in a C string.
// Escape for the outer C layer only; addslashes() leaves line
// breaks and other control bytes untouched, producing invalid C++.
return '"' . getTranslator()->escapeString($this->defaultValue) . '"';
}
return "NULL";
@ -2485,7 +2488,9 @@ class EvaluatedValue
if ($forStringDef === '') {
$forStringDef = "{$zvalName}_str";
}
$code .= "\tzend_string *$forStringDef = zend_string_init($cExpr, strlen($cExpr), 1);\n";
// getCExpr() emits a C string literal here. sizeof() preserves
// embedded NUL bytes, unlike strlen().
$code .= "\tzend_string *$forStringDef = zend_string_init($cExpr, sizeof($cExpr) - 1, 1);\n";
$code .= "\tZVAL_STR(&$zvalName, $forStringDef);\n";
}
} elseif ($this->type->isArray()) {
@ -2515,20 +2520,12 @@ class EvaluatedValue
return '"' . getTranslator()->escapeString((string) $this->value) . '"';
} elseif ($this->expr instanceof Expr\ConstFetch) {
return getTranslator()->getConstValue($this->expr->name->toString());
} elseif ($this->expr instanceof String_) {
// Heredoc/nowdoc and quoted string literals: emit the decoded
// value directly. Pretty-printing a heredoc/nowdoc would leak
// the `<<<MARKER ... MARKER` source syntax into the generated
// C++, which is invalid; for quoted strings this is also the
// correct, simpler form.
return '"' . getTranslator()->escapeString((string) $this->value) . '"';
} else {
// ConstExprEvaluator has already reduced concatenations and
// other constant string expressions to their PHP value. Emit
// that value as a C string literal instead of rejecting every
// non-literal string expression.
return '"' . getTranslator()->escapeString((string) $this->value) . '"';
}
// ConstExprEvaluator has already decoded literal syntax and
// reduced constant string expressions. Emitting that value avoids
// leaking heredoc/nowdoc source syntax into generated C++.
return '"' . getTranslator()->escapeString((string) $this->value) . '"';
} elseif ($this->type->isInt() or $this->type->isFloat()) {
return strval($this->value);
} elseif ($this->type->isBool()) {
@ -5113,13 +5110,10 @@ function parseFunctionLike(
if ($param->default instanceof Expr\ClassConstFetch && $param->default->class->toLowerString() === "self") {
$defaultValue = getTranslator()->getClassConstValue($func, $name->className->name, $param->default->name->name);
$defaultValue = var_export($defaultValue, true);
} elseif ($param->default instanceof String_ &&
in_array($param->default->getAttribute('kind'), [String_::KIND_HEREDOC, String_::KIND_NOWDOC], true)
) {
// heredoc/nowdoc: prettyPrint 会输出 `<<<MARKER ... MARKER` 源码语法
// 在 arginfo 的 C 字符串字面量中属于非法内容;改为输出解码后的字符串值
// (PHP 双引号字符串字面量,经 addslashes 包裹后仍可被 arginfo 正确求值)。
$defaultValue = '"' . getTranslator()->escapeString((string) $param->default->value) . '"';
} elseif ($param->default instanceof String_) {
// Keep this as a PHP expression. ArgInfo escapes the expression
// separately when embedding it in generated C++.
$defaultValue = var_export($param->default->value, true);
} else {
$defaultValue = $param->default ? $prettyPrinter->prettyPrintExpr($param->default) : null;
}

@ -6,18 +6,18 @@ class constants with heredoc and nowdoc syntax
class Test
{
const VALUE1 = <<<ABC
abc
quote " slash \\ nul \0 tab \t ??
ABC;
const VALUE2 = <<<'DEF'
def
$value ?? "quoted" \n \path
DEF;
}
function main()
{
var_dump(Test::VALUE1, Test::VALUE2);
var_dump(bin2hex(Test::VALUE1), bin2hex(Test::VALUE2));
}
?>
--EXPECT--
string(3) "abc"
string(3) "def"
string(60) "71756f7465202220736c617368205c206e756c2000207461622009203f3f"
string(54) "2476616c7565203f3f202271756f74656422205c6e205c70617468"

@ -17,8 +17,14 @@ class WithProp
ABC;
}
function with_default(string $x = <<<ABC
abc
function with_default(string $x = <<<'ABC'
$value ?? "quoted" \n \path
ABC): string {
return $x;
}
function with_binary_default(string $x = <<<ABC
A\0B
ABC): string {
return $x;
}
@ -28,11 +34,18 @@ function main()
var_dump(G_HEREDOC, G_NOWDOC);
$o = new WithProp();
var_dump($o->p);
var_dump(with_default());
var_dump(bin2hex(with_default()), bin2hex(with_binary_default()));
$default = (new ReflectionFunction('with_default'))->getParameters()[0]->getDefaultValue();
$binaryDefault = (new ReflectionFunction('with_binary_default'))->getParameters()[0]->getDefaultValue();
var_dump(bin2hex($default), bin2hex($binaryDefault));
}
?>
--EXPECT--
string(3) "abc"
string(3) "def"
string(3) "xyz"
string(3) "abc"
string(54) "2476616c7565203f3f202271756f74656422205c6e205c70617468"
string(6) "410042"
string(54) "2476616c7565203f3f202271756f74656422205c6e205c70617468"
string(6) "410042"

Loading…
Cancel
Save