Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions ext/standard/substr_compare.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public function execute(Frame $frame): void
$argc = \count($frame->calledArgs);
$haystack = self::vmStringArg($frame, 0, 'haystack');
$needle = self::vmStringArg($frame, 1, 'needle');
$offsetInt = self::requireIntArg($frame->calledArgs[2], 'substr_compare', 3, 'offset');
// Z_PARAM_LONG $offset — soft-null DEP+coerce (php-src string.c; #29504; peer substr_count #21657).
$offsetInt = VmMath::parseChrCodepointForFrame($frame, 2, 'substr_compare', 3, 'offset');
$length = null;
if ($argc >= 4) {
$lengthArg = $frame->calledArgs[3]->resolveIndirect();
Expand Down Expand Up @@ -117,15 +118,16 @@ public function call(Context $context, JITVariable ...$args): Value
// Soft-null on forward profile — Zend 8.4 deprecate+coerce (#21515, reverts #20164 TypeError).
$p0 = $this->stringDataPtr($context, self::jitStringArg($context, $args[0], 0, 'haystack'));
$p1 = $this->stringDataPtr($context, self::jitStringArg($context, $args[1], 1, 'needle'));
$offset = self::lowerStrictIntArg($context, $args[2], 'substr_compare', 3, 'offset');
// Z_PARAM_LONG $offset — soft-null DEP+coerce (#29504; peer substr_count #21657).
$offset = JitChr::lowerZParamLongArg($context, $args[2], 'substr_compare', 3, 'offset');
$fn = $context->lookupFunction('substr_compare');
$raw = $context->builder->call($fn, $p0, $p1, $offset, $lengthVal, $ci);

return $context->builder->sExt($raw, $i64);
}

/**
* Compile-time soft-null fold — emit DEP then host-evaluate when all operands are literals (#21515).
* Compile-time soft-null fold — emit DEP then host-evaluate when all operands are literals (#21515 / #29504).
*/
private static function tryFoldCompileTimeSoftNull(Context $context, array $args): ?Value
{
Expand All @@ -134,18 +136,22 @@ private static function tryFoldCompileTimeSoftNull(Context $context, array $args
}
$hayNull = self::isCompileTimeNull($args[0]);
$needleNull = self::isCompileTimeNull($args[1]);
if (!$hayNull && !$needleNull) {
$offsetNull = self::isCompileTimeNull($args[2]);
if (!$hayNull && !$needleNull && !$offsetNull) {
return null;
}
$hayLit = $hayNull ? '' : JitStringArg::compileTimeLiteral($args[0]);
$needleLit = $needleNull ? '' : JitStringArg::compileTimeLiteral($args[1]);
if (null === $hayLit || null === $needleLit) {
return null;
}
if (null === $args[2]->compileTimeLong) {
if ($offsetNull) {
$offset = 0;
} elseif (null === $args[2]->compileTimeLong) {
return null;
} else {
$offset = $args[2]->compileTimeLong;
}
$offset = $args[2]->compileTimeLong;
$argc = \count($args);
$length = null;
if ($argc >= 4) {
Expand All @@ -170,6 +176,9 @@ private static function tryFoldCompileTimeSoftNull(Context $context, array $args
if ($needleNull) {
JitStringBuiltinArg::lowerTrimFamilyString($context, $args[1], 'substr_compare', 1, 'needle');
}
if ($offsetNull) {
JitIntdiv::emitNullIntDeprecation($context, 'substr_compare', 3, 'offset');
}

return $context->getTypeFromString('int64')->constInt(
VmString::substr_compare($hayLit, $needleLit, $offset, $length, $caseInsensitive),
Expand Down
2 changes: 2 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@
<file>./test/compliance/HtmlspecialcharsDoubleEncodeNullJITTest.php</file>
<file>./test/compliance/SubstrCountNullNeedleForward84VMTest.php</file>
<file>./test/compliance/SubstrCountNullNeedleForward84JITTest.php</file>
<file>./test/compliance/SubstrCompareNullOffsetVMTest.php</file>
<file>./test/compliance/SubstrCompareNullOffsetJITTest.php</file>
</testsuite>
<testsuite name="PHPCompiler Unit Test Suites">
<directory suffix=".php">./test/unit</directory>
Expand Down
4 changes: 4 additions & 0 deletions test/compliance/SubstrCompareJITTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ public static function providePHPTests(): \Generator
__DIR__.'/cases/stdlib/substr_compare_explicit_length_jit.phpt',
'substr_compare_explicit_length_jit.phpt'
);
yield 'substr_compare_null_offset_jit.phpt' => self::parsePHPT(
__DIR__.'/cases/stdlib/substr_compare_null_offset_jit.phpt',
'substr_compare_null_offset_jit.phpt'
);
}

public function setUp(): void
Expand Down
29 changes: 29 additions & 0 deletions test/compliance/SubstrCompareNullOffsetJITTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

require_once __DIR__.'/../BaseTest.php';

/**
* @group llvm
*/
/** JIT: substr_compare(null $offset) soft-null DEP+coerce (#29504, php-src string.c). */
final class SubstrCompareNullOffsetJITTest extends BaseTest
{
protected static string $DIR = __DIR__;

public static function providePHPTests(): \Generator
{
yield 'substr_compare_null_offset_jit.phpt' => self::parsePHPT(
__DIR__.'/cases/stdlib/substr_compare_null_offset_jit.phpt',
'substr_compare_null_offset_jit.phpt'
);
}

public function setUp(): void
{
$this->BIN = realpath(__DIR__.'/../../bin/jit.php');
}
}
26 changes: 26 additions & 0 deletions test/compliance/SubstrCompareNullOffsetVMTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

require_once __DIR__.'/../BaseTest.php';

/** VM: substr_compare(null $offset) soft-null DEP+coerce (#29504, php-src string.c). */
final class SubstrCompareNullOffsetVMTest extends BaseTest
{
protected static string $DIR = __DIR__;

public static function providePHPTests(): \Generator
{
yield 'substr_compare_null_offset.phpt' => self::parsePHPT(
__DIR__.'/cases/stdlib/substr_compare_null_offset.phpt',
'substr_compare_null_offset.phpt'
);
}

public function setUp(): void
{
$this->BIN = realpath(__DIR__.'/../../bin/vm.php');
}
}
4 changes: 4 additions & 0 deletions test/compliance/SubstrCompareVMTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public static function providePHPTests(): \Generator
__DIR__.'/cases/stdlib/substr_compare_named.phpt',
'substr_compare_named.phpt'
);
yield 'substr_compare_null_offset.phpt' => self::parsePHPT(
__DIR__.'/cases/stdlib/substr_compare_null_offset.phpt',
'substr_compare_null_offset.phpt'
);
}

public function setUp(): void
Expand Down
23 changes: 23 additions & 0 deletions test/compliance/cases/stdlib/substr_compare_null_offset.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
stdlib substr_compare(null $offset) soft-null DEP+coerce (#29504, ext/standard/string.c Z_PARAM_LONG)
--FILE--
<?php
set_error_handler(static function (int $no, string $msg): bool {
if (E_DEPRECATED === $no) {
echo "DEP\n";

return true;
}

return false;
});
try {
$r = substr_compare('abc', 'b', null);
echo ($r === -1 ? 'OK' : 'BAD '.var_export($r, true)), "\n";
} catch (Throwable $e) {
echo get_class($e), ':', $e->getMessage(), "\n";
}
?>
--EXPECT--
DEP
OK
24 changes: 24 additions & 0 deletions test/compliance/cases/stdlib/substr_compare_null_offset_jit.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
JIT: substr_compare(null $offset) soft-null DEP+coerce (#29504, ext/standard/string.c Z_PARAM_LONG)
--JIT--
--FILE--
<?php
set_error_handler(static function (int $no, string $msg): bool {
if (E_DEPRECATED === $no) {
echo "DEP\n";

return true;
}

return false;
});
try {
$r = substr_compare('abc', 'b', null);
echo ($r === -1 ? 'OK' : 'BAD '.var_export($r, true)), "\n";
} catch (Throwable $e) {
echo get_class($e), ':', $e->getMessage(), "\n";
}
?>
--EXPECT--
DEP
OK
2 changes: 1 addition & 1 deletion test/compliance/cases/stdlib/substr_compare_typeerror.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ try {
echo $e->getMessage(), "\n";
}
try {
substr_compare('abc', 'ab', '0', 2);
substr_compare('abc', 'ab', 'x', 2);
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ try {
echo $e->getMessage(), "\n";
}
try {
substr_compare('abc', 'ab', '0', 2);
substr_compare('abc', 'ab', 'x', 2);
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
$cases = [
'haystack' => [static fn () => substr_compare(null, 'a', 0), -1],
'needle' => [static fn () => substr_compare('abc', null, 0), 1],
// #29504 — Z_PARAM_LONG $offset soft-null (peer substr_count #21657).
'offset' => [static fn () => substr_compare('abc', 'b', null), -1],
];
foreach ($cases as $label => [$factory, $expect]) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ DEP
haystack OK
DEP
needle OK
DEP
offset OK
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ DEP
haystack OK
DEP
needle OK
DEP
offset OK
9 changes: 9 additions & 0 deletions test/fixtures/aot/cases/substr_compare_null_offset.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
AOT: substr_compare(null $offset) soft-null coerce (#29504, ext/standard/string.c Z_PARAM_LONG)
--FILE--
<?php
// DEP is verified on VM/JIT; AOT checks coerce result (offset 0 → 'abc' vs 'b' → -1).
echo substr_compare('abc', 'b', null), "\n";
?>
--EXPECT--
-1
14 changes: 14 additions & 0 deletions test/repro/issue_29504_substr_compare_null_offset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
// #29504 — substr_compare(..., null) $offset soft-null DEP+coerce (php-src-strict)
error_reporting(E_ALL);
set_error_handler(static function (int $no, string $str): bool {
echo "WARN[$no]: $str\n";

return true;
});
try {
var_export(substr_compare('abc', 'b', null));
echo "\n";
} catch (Throwable $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
3 changes: 3 additions & 0 deletions test/repro/issue_29504_substr_compare_null_offset_aot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php
// #29504 — AOT smoke: null $offset coerces to 0 (DEP verified on VM/JIT).
echo substr_compare('abc', 'b', null), "\n";
Loading