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
51 changes: 35 additions & 16 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 | 196 |
| PHP files on vm.php path | 199 |
| Source constructs flagged (blockers) | 10 |
| Source constructs flagged (warnings) | 523 |
| Source constructs flagged (warnings) | 527 |

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

Expand Down Expand Up @@ -40,16 +40,18 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
| `ext/standard/JitHtmlspecialchars.php` | 0 | 1 |
| `ext/standard/JitImplode.php` | 0 | 1 |
| `ext/standard/JitNumberFormat.php` | 0 | 1 |
| `ext/standard/JitParseUrl.php` | 0 | 1 |
| `ext/standard/JitPath.php` | 0 | 1 |
| `ext/standard/JitRealpath.php` | 0 | 1 |
| `ext/standard/JitStrPad.php` | 0 | 1 |
| `ext/standard/JitStrRepeat.php` | 0 | 1 |
| `ext/standard/JitStrReplace.php` | 0 | 1 |
| `ext/standard/JitStringConcat.php` | 0 | 1 |
| `ext/standard/JitStringIndex.php` | 0 | 1 |
| `ext/standard/JitStripTags.php` | 0 | 1 |
| `ext/standard/JitStrpos.php` | 0 | 1 |
| `ext/standard/JitUrlencode.php` | 0 | 1 |
| `ext/standard/Module.php` | 0 | 94 |
| `ext/standard/Module.php` | 0 | 95 |
| `ext/standard/VmFs.php` | 0 | 3 |
| `ext/standard/VmNumberFormat.php` | 0 | 1 |
| `ext/standard/VmString.php` | 0 | 2 |
Expand Down Expand Up @@ -135,6 +137,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
| `ext/standard/string_ltrim.php` | 0 | 1 |
| `ext/standard/string_rtrim.php` | 0 | 1 |
| `ext/standard/string_trim.php` | 0 | 1 |
| `ext/standard/strip_tags.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 @@ -265,6 +268,11 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
**Warnings** (review for bootstrap subset):
- 4 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `ext/standard/JitParseUrl.php`

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

### `ext/standard/JitPath.php`

**Warnings** (review for bootstrap subset):
Expand Down Expand Up @@ -300,6 +308,11 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
**Warnings** (review for bootstrap subset):
- 5 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `ext/standard/JitStripTags.php`

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

### `ext/standard/JitStrpos.php`

**Warnings** (review for bootstrap subset):
Expand Down Expand Up @@ -394,18 +407,19 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
- new str_pad (line 100)
- new str_split (line 101)
- new htmlspecialchars (line 102)
- new header_ (line 103)
- new http_response_code (line 104)
- new urlencode (line 105)
- new rawurlencode (line 106)
- new parse_url (line 107)
- new dirname (line 108)
- new basename (line 109)
- new realpath (line 110)
- new getenv_ (line 111)
- new putenv_ (line 112)
- new scandir (line 113)
- new glob_ (line 114)
- new strip_tags (line 103)
- new header_ (line 104)
- new http_response_code (line 105)
- new urlencode (line 106)
- new rawurlencode (line 107)
- new parse_url (line 108)
- new dirname (line 109)
- new basename (line 110)
- new realpath (line 111)
- new getenv_ (line 112)
- new putenv_ (line 113)
- new scandir (line 114)
- new glob_ (line 115)
- 2 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `ext/standard/VmFs.php`
Expand All @@ -423,7 +437,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
### `ext/standard/VmString.php`

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

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

### `ext/standard/strip_tags.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
6 changes: 4 additions & 2 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Wrote /compiler/docs/capabilities.md (105 builtins).
t/capability-matrix.php`. Do not edit by hand.
# Builtin capability matrix

Auto-generated by `script/capability-matrix.php`. Do not edit by hand.

| Function | VM | JIT | AOT | Module | Notes |
|----------|:--:|:---:|:---:|--------|-------|
Expand Down Expand Up @@ -96,6 +97,7 @@ t/capability-matrix.php`. Do not edit by hand.
| `str_split` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build |
| `str_starts_with` | yes | yes | yes | standard | AOT PHPT |
| `strcmp` | yes | yes | yes | standard | |
| `strip_tags` | 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
78 changes: 78 additions & 0 deletions ext/standard/JitStripTags.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPLLVM\Value;

/**
* LLVM JIT/AOT helpers for strip_tags() (mirrors VmString::stripTags).
*/
final class JitStripTags
{
public static function stripTags(Context $context, JITVariable $input, ?JITVariable $allowed = null): Value
{
$inputLiteral = $input->compileTimeString ?? null;
if (null !== $inputLiteral) {
$allowedLiteral = null;
if (null !== $allowed) {
if (JITVariable::TYPE_STRING === $allowed->type) {
$allowedLiteral = $allowed->compileTimeString ?? '';
} elseif (JITVariable::TYPE_VALUE !== $allowed->type) {
throw new \LogicException(
'strip_tags() allowed_tags must be a string or null in this compiler build'
);
}
}

return $context->builder->load(
$context->constantStringFromString(VmString::stripTags($inputLiteral, $allowedLiteral))
);
}

$inPtr = self::jitStringArg($context, $input);
$allowPtr = self::jitAllowedArg($context, $allowed);

return $context->builder->call(
$context->lookupFunction('__compiler_strip_tags'),
$inPtr,
$allowPtr
);
}

private static function jitStringArg(Context $context, JITVariable $arg): Value
{
if (JITVariable::TYPE_STRING === $arg->type) {
return $context->helper->loadValue($arg);
}
if (JITVariable::TYPE_VALUE === $arg->type) {
return $context->builder->call(
$context->lookupFunction('__value__readString'),
$arg->value
);
}

throw new \LogicException('strip_tags() only supports strings in this compiler build');
}

private static function jitAllowedArg(Context $context, ?JITVariable $allowed): Value
{
if (null === $allowed) {
return $context->builder->load($context->constantStringFromString(''));
}
if (JITVariable::TYPE_STRING === $allowed->type) {
return $context->helper->loadValue($allowed);
}
if (JITVariable::TYPE_VALUE === $allowed->type) {
return $context->builder->call(
$context->lookupFunction('__value__readString'),
$allowed->value
);
}

throw new \LogicException('strip_tags() allowed_tags must be a string or null in this compiler build');
}
}
1 change: 1 addition & 0 deletions ext/standard/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public function getFunctions(): array
new str_pad(),
new str_split(),
new htmlspecialchars(),
new strip_tags(),
new header_(),
new http_response_code(),
new urlencode(),
Expand Down
128 changes: 128 additions & 0 deletions ext/standard/VmString.php
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,134 @@ public static function htmlspecialchars(
return $out;
}

/**
* strip_tags() subset: removes HTML/PHP tags; optional allow-list like "<b><p>".
* HTML comments and PHP tags remove their inner content; other tags keep inner text.
*/
public static function stripTags(string $string, ?string $allowedTags = null): string
{
$allowed = null === $allowedTags || '' === $allowedTags
? []
: self::parseAllowedTags($allowedTags);
$out = '';
$len = self::byteLength($string);
$i = 0;
while ($i < $len) {
$ch = $string[$i];
if ('<' !== $ch) {
$out .= $ch;
++$i;
continue;
}
if ($i + 3 < $len && '<!--' === self::byteSlice($string, $i, 4)) {
$end = self::findSubstring($string, '-->', $i + 4);
if (false !== $end) {
$i = $end + 3;
continue;
}
}
if ($i + 1 < $len && '<?' === self::byteSlice($string, $i, 2)) {
$end = self::findSubstring($string, '?>', $i + 2);
if (false !== $end) {
$i = $end + 2;
continue;
}
}
$gt = self::findSubstring($string, '>', $i + 1);
if (false === $gt) {
$out .= $ch;
++$i;
continue;
}
$tagContent = self::byteSlice($string, $i + 1, $gt - $i - 1);
$tagName = self::extractTagName($tagContent);
if (null !== $tagName && [] !== $allowed && self::isTagAllowed($tagName, $allowed)) {
$out .= self::byteSlice($string, $i, $gt - $i + 1);
}
$i = $gt + 1;
}

return $out;
}

/**
* @return list<string>
*/
private static function parseAllowedTags(string $allowedTags): array
{
$tags = [];
$len = self::byteLength($allowedTags);
$i = 0;
while ($i < $len) {
if ('<' !== $allowedTags[$i]) {
++$i;
continue;
}
$gt = self::findSubstring($allowedTags, '>', $i + 1);
if (false === $gt) {
break;
}
$name = self::extractTagName(self::byteSlice($allowedTags, $i + 1, $gt - $i - 1));
if (null !== $name && '' !== $name) {
$tags[] = $name;
}
$i = $gt + 1;
}

return $tags;
}

private static function extractTagName(string $tagContent): ?string
{
$len = self::byteLength($tagContent);
$i = 0;
while ($i < $len && self::isTagWhitespace($tagContent[$i])) {
++$i;
}
if ($i < $len && '/' === $tagContent[$i]) {
++$i;
}
if ($i >= $len) {
return null;
}
$start = $i;
while ($i < $len) {
$ch = $tagContent[$i];
if (self::isTagWhitespace($ch) || '>' === $ch || '/' === $ch) {
break;
}
if (!ctype_alpha($ch) && !ctype_digit($ch)) {
return null;
}
++$i;
}
if ($start === $i) {
return null;
}

return strtolower(self::byteSlice($tagContent, $start, $i - $start));
}

/**
* @param list<string> $allowed
*/
private static function isTagAllowed(string $tagName, array $allowed): bool
{
$tagName = strtolower($tagName);
foreach ($allowed as $name) {
if ($tagName === $name) {
return true;
}
}

return false;
}

private static function isTagWhitespace(string $ch): bool
{
return str_contains(self::TRIM_DEFAULT, $ch);
}

/**
* @return list<string>
*/
Expand Down
Loading
Loading