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
129 changes: 68 additions & 61 deletions docs/bootstrap-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Regenerate: `php script/bootstrap-inventory.php`

| Metric | Count |
|--------|------:|
| PHP files on vm.php path | 238 |
| PHP files on vm.php path | 239 |
| Source constructs flagged (blockers) | 10 |
| Source constructs flagged (warnings) | 626 |
| Source constructs flagged (warnings) | 628 |

## Compiler CFG gaps (`lib/Compiler.php`)

Expand Down Expand Up @@ -58,7 +58,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
| `ext/standard/JitStripTags.php` | 0 | 1 |
| `ext/standard/JitStrpos.php` | 0 | 1 |
| `ext/standard/JitUrlencode.php` | 0 | 1 |
| `ext/standard/Module.php` | 0 | 109 |
| `ext/standard/Module.php` | 0 | 110 |
| `ext/standard/VmDate.php` | 0 | 1 |
| `ext/standard/VmExit.php` | 0 | 2 |
| `ext/standard/VmFs.php` | 0 | 3 |
Expand Down Expand Up @@ -159,6 +159,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
| `ext/standard/string_rtrim.php` | 0 | 1 |
| `ext/standard/string_trim.php` | 0 | 1 |
| `ext/standard/strip_tags.php` | 0 | 1 |
| `ext/standard/stripos.php` | 0 | 1 |
| `ext/standard/strncmp.php` | 0 | 1 |
| `ext/standard/strpos.php` | 0 | 1 |
| `ext/standard/strrev.php` | 0 | 1 |
Expand Down Expand Up @@ -449,64 +450,65 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
- new substr (line 69)
- new strrev (line 70)
- new strpos (line 71)
- new str_contains (line 72)
- new str_starts_with (line 73)
- new str_ends_with (line 74)
- new strncmp (line 75)
- new array_count (line 76)
- new stripos (line 72)
- new str_contains (line 73)
- new str_starts_with (line 74)
- new str_ends_with (line 75)
- new strncmp (line 76)
- new array_count (line 77)
- new array_key_exists (line 78)
- new in_array (line 79)
- new array_push (line 80)
- new array_pop (line 81)
- new array_shift (line 82)
- new sort_ (line 83)
- new array_values (line 84)
- new array_keys (line 85)
- new array_merge (line 86)
- new array_slice (line 87)
- new explode (line 88)
- new implode (line 89)
- new str_replace (line 90)
- new nl2br (line 91)
- new array_reverse (line 92)
- new array_search (line 93)
- new array_sum (line 94)
- new array_flip (line 95)
- new array_unique (line 96)
- new array_fill (line 97)
- new array_combine (line 98)
- new range (line 99)
- new bin2hex (line 100)
- new hex2bin (line 101)
- new random_bytes (line 102)
- new str_pad (line 103)
- new str_split (line 104)
- new htmlspecialchars (line 105)
- new strip_tags (line 106)
- new header_ (line 107)
- new header_remove (line 108)
- new header_list (line 109)
- new getallheaders_ (line 110)
- new http_response_code (line 111)
- new urlencode (line 112)
- new rawurlencode (line 113)
- new urldecode (line 114)
- new rawurldecode (line 115)
- new parse_url (line 116)
- new dirname (line 117)
- new basename (line 118)
- new realpath (line 119)
- new file_get_contents (line 120)
- new getenv_ (line 121)
- new putenv_ (line 122)
- new extract_ (line 123)
- new compact_ (line 124)
- new scandir (line 125)
- new glob_ (line 126)
- new time (line 127)
- new date (line 128)
- new gmdate (line 129)
- new array_count (line 78)
- new array_key_exists (line 79)
- new in_array (line 80)
- new array_push (line 81)
- new array_pop (line 82)
- new array_shift (line 83)
- new sort_ (line 84)
- new array_values (line 85)
- new array_keys (line 86)
- new array_merge (line 87)
- new array_slice (line 88)
- new explode (line 89)
- new implode (line 90)
- new str_replace (line 91)
- new nl2br (line 92)
- new array_reverse (line 93)
- new array_search (line 94)
- new array_sum (line 95)
- new array_flip (line 96)
- new array_unique (line 97)
- new array_fill (line 98)
- new array_combine (line 99)
- new range (line 100)
- new bin2hex (line 101)
- new hex2bin (line 102)
- new random_bytes (line 103)
- new str_pad (line 104)
- new str_split (line 105)
- new htmlspecialchars (line 106)
- new strip_tags (line 107)
- new header_ (line 108)
- new header_remove (line 109)
- new header_list (line 110)
- new getallheaders_ (line 111)
- new http_response_code (line 112)
- new urlencode (line 113)
- new rawurlencode (line 114)
- new urldecode (line 115)
- new rawurldecode (line 116)
- new parse_url (line 117)
- new dirname (line 118)
- new basename (line 119)
- new realpath (line 120)
- new file_get_contents (line 121)
- new getenv_ (line 122)
- new putenv_ (line 123)
- new extract_ (line 124)
- new compact_ (line 125)
- new scandir (line 126)
- new glob_ (line 127)
- new time (line 128)
- new date (line 129)
- new gmdate (line 130)
- 2 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `ext/standard/VmDate.php`
Expand Down Expand Up @@ -544,7 +546,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
**Warnings** (review for bootstrap subset):
- new Exception (line 182)
- new Exception (line 190)
- 52 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler
- 56 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler
- 1 closure(s)

### `ext/standard/abs.php`
Expand Down Expand Up @@ -1041,6 +1043,11 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
**Warnings** (review for bootstrap subset):
- 2 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `ext/standard/stripos.php`

**Warnings** (review for bootstrap subset):
- 2 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `ext/standard/strncmp.php`

**Warnings** (review for bootstrap subset):
Expand Down
1 change: 1 addition & 0 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand.
| `str_starts_with` | yes | yes | yes | standard | AOT PHPT |
| `strcmp` | yes | yes | yes | standard | |
| `strip_tags` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
| `stripos` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
| `strlen` | yes | yes | yes | types | JIT PHPT; AOT PHPT |
| `strncmp` | yes | yes | yes | standard | |
| `strpos` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
Expand Down
6 changes: 4 additions & 2 deletions ext/standard/JitStrpos.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public static function find(
Context $context,
Value $haystack,
Value $needle,
?Value $offset = null
?Value $offset = null,
bool $caseInsensitive = false
): Value {
$map = $context->structFieldMap['__string__'];
$hayLen = $context->builder->load(
Expand All @@ -39,8 +40,9 @@ public static function find(
$searchPtr = $context->builder->inBoundsGEP($hayPtr, $clamped);
}

$searchFn = $caseInsensitive ? 'strcasestr' : 'strstr';
$found = $context->builder->call(
$context->lookupFunction('strstr'),
$context->lookupFunction($searchFn),
$searchPtr,
$needlePtr
);
Expand Down
9 changes: 9 additions & 0 deletions ext/standard/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public function getFunctions(): array
new substr(),
new strrev(),
new strpos(),
new stripos(),
new str_contains(),
new str_starts_with(),
new str_ends_with(),
Expand Down Expand Up @@ -159,6 +160,14 @@ public function jitInit(JIT\Context $context): void
$fn = $context->module->addFunction('strstr', $ft);
$context->registerFunction('strstr', $fn);
}
try {
$context->lookupFunction('strcasestr');
} catch (\Throwable $e) {
$i8p = $context->getTypeFromString('int8*');
$ft = $context->context->functionType($i8p, false, $i8p, $i8p);
$fn = $context->module->addFunction('strcasestr', $ft);
$context->registerFunction('strcasestr', $fn);
}
try {
$context->lookupFunction('strtol');
} catch (\Throwable $e) {
Expand Down
60 changes: 60 additions & 0 deletions ext/standard/VmString.php
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,22 @@ public static function strpos(string $haystack, string $needle, int $offset = 0)
return false === $pos ? false : $pos;
}

/**
* @return int|false
*/
public static function stripos(string $haystack, string $needle, int $offset = 0)
{
if ('' === $needle) {
throw new \LogicException('stripos(): Argument #2 ($needle) cannot be empty');
}
if ($offset < 0) {
$offset = 0;
}
$pos = self::findSubstringCaseInsensitive($haystack, $needle, $offset);

return false === $pos ? false : $pos;
}

public static function startsWith(string $haystack, string $needle): bool
{
$nlen = self::byteLength($needle);
Expand Down Expand Up @@ -850,6 +866,27 @@ private static function compareBytes(string $haystack, string $needle, int $leng
return true;
}

private static function compareBytesCaseInsensitive(string $haystack, string $needle, int $length, int $hayOffset = 0): bool
{
for ($i = 0; $i < $length; ++$i) {
if (self::asciiLowerByte($haystack[$hayOffset + $i]) !== self::asciiLowerByte($needle[$i])) {
return false;
}
}

return true;
}

private static function asciiLowerByte(string $byte): string
{
$ord = self::byteOrd($byte);
if ($ord >= 65 && $ord <= 90) {
return self::byteChr($ord + 32);
}

return $byte;
}

/**
* @return int|false
*/
Expand All @@ -873,6 +910,29 @@ private static function findSubstring(string $haystack, string $needle, int $off
return false;
}

/**
* @return int|false
*/
private static function findSubstringCaseInsensitive(string $haystack, string $needle, int $offset)
{
$hayLen = self::byteLength($haystack);
$needleLen = self::byteLength($needle);
if (0 === $needleLen) {
return false;
}
if ($offset >= $hayLen) {
return false;
}
$limit = $hayLen - $needleLen;
for ($i = $offset; $i <= $limit; ++$i) {
if (self::compareBytesCaseInsensitive($haystack, $needle, $needleLen, $i)) {
return $i;
}
}

return false;
}

private static function asciiCaseTransform(string $string, bool $toLower): string
{
$out = '';
Expand Down
73 changes: 73 additions & 0 deletions ext/standard/stripos.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

use PHPCompiler\Frame;
use PHPCompiler\Func\Internal;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPCompiler\VM\Variable;
use PHPLLVM\Value;

/**
* stripos() for two strings (subset of PHP; ASCII case fold, non-empty needle, no offset in JIT).
*/
final class stripos extends Internal
{
public function execute(Frame $frame): void
{
$argc = count($frame->calledArgs);
if ($argc < 2 || $argc > 3) {
throw new \LogicException('stripos() requires two or three arguments');
}
$haystack = $frame->calledArgs[0]->resolveIndirect();
$needle = $frame->calledArgs[1]->resolveIndirect();
if (null === $frame->returnVar) {
return;
}
if (Variable::TYPE_STRING !== $haystack->type || Variable::TYPE_STRING !== $needle->type) {
throw new \LogicException('stripos() only supports strings in this compiler build');
}
$offset = 0;
if (3 === $argc) {
$offVar = $frame->calledArgs[2]->resolveIndirect();
if (Variable::TYPE_INTEGER !== $offVar->type) {
throw new \LogicException('stripos() offset must be an integer in this compiler build');
}
$offset = $offVar->toInt();
}
$result = VmString::stripos($haystack->toString(), $needle->toString(), $offset);
if (false === $result) {
$frame->returnVar->bool(false);
} else {
$frame->returnVar->int($result);
}
}

public Context $context;

public function call(Context $context, JITVariable ...$args): Value
{
$this->context = $context;
$argc = count($args);
if ($argc < 2 || $argc > 3) {
throw new \LogicException('stripos() requires two or three arguments');
}
if (JITVariable::TYPE_STRING !== $args[0]->type || JITVariable::TYPE_STRING !== $args[1]->type) {
throw new \LogicException('stripos() only supports strings in this compiler build');
}
if (3 === $argc && JITVariable::TYPE_NATIVE_LONG !== $args[2]->type) {
throw new \LogicException('stripos() offset must be an integer in this compiler build');
}

$hay = $context->helper->loadValue($args[0]);
$needle = $context->helper->loadValue($args[1]);
$offset = 3 === $argc
? $context->builder->truncOrBitCast($context->helper->loadValue($args[2]), $context->getTypeFromString('int64'))
: null;

return JitStrpos::find($context, $hay, $needle, $offset, true);
}
}
11 changes: 11 additions & 0 deletions test/compliance/cases/stdlib/stripos.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
stdlib stripos()
--FILE--
<?php
echo stripos('Hello', 'LL'), "\n";
echo stripos('Hello', 'x') == false ? 'y' : 'n', "\n";
echo stripos('Hello', 'L', 3), "\n";
--EXPECT--
2
y
3
Loading
Loading