diff --git a/docs/bootstrap-inventory.md b/docs/bootstrap-inventory.md index 658e75ca..e0d23d8c 100644 --- a/docs/bootstrap-inventory.md +++ b/docs/bootstrap-inventory.md @@ -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`) @@ -40,6 +40,7 @@ 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 | @@ -47,9 +48,10 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered: | `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 | @@ -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 | @@ -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): @@ -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): @@ -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` @@ -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` @@ -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): diff --git a/docs/capabilities.md b/docs/capabilities.md index f275e374..41a80b80 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -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 | |----------|:--:|:---:|:---:|--------|-------| @@ -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 | diff --git a/ext/standard/JitStripTags.php b/ext/standard/JitStripTags.php new file mode 100644 index 00000000..574f3344 --- /dev/null +++ b/ext/standard/JitStripTags.php @@ -0,0 +1,78 @@ +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'); + } +} diff --git a/ext/standard/Module.php b/ext/standard/Module.php index 9e2e0e00..1014e5df 100755 --- a/ext/standard/Module.php +++ b/ext/standard/Module.php @@ -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(), diff --git a/ext/standard/VmString.php b/ext/standard/VmString.php index ebc9b692..0381ffd5 100644 --- a/ext/standard/VmString.php +++ b/ext/standard/VmString.php @@ -353,6 +353,134 @@ public static function htmlspecialchars( return $out; } + /** + * strip_tags() subset: removes HTML/PHP tags; optional allow-list like "
".
+ * 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 && '', $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 a a a a
b', '
'), "\n";
+echo strip_tags('ab'), "\n";
+echo strip_tags('not a tag < incomplete'), "\n";
+--EXPECT--
+alert(1)hello
+xy
+
b
+ab
+not a tag < incomplete
diff --git a/test/compliance/cases/stdlib/strip_tags_jit.phpt b/test/compliance/cases/stdlib/strip_tags_jit.phpt
new file mode 100644
index 00000000..9a43b1c9
--- /dev/null
+++ b/test/compliance/cases/stdlib/strip_tags_jit.phpt
@@ -0,0 +1,13 @@
+--TEST--
+stdlib strip_tags() JIT/AOT path
+--FILE--
+alert(1)hello'), "\n";
+echo strip_tags('xy', ''), "\n";
+echo strip_tags('
b', '
'), "\n";
+echo strip_tags('ab'), "\n";
+--EXPECT--
+alert(1)hello
+xy
+
b
+ab
diff --git a/test/fixtures/aot/cases/strip_tags.phpt b/test/fixtures/aot/cases/strip_tags.phpt
new file mode 100644
index 00000000..4e8c7477
--- /dev/null
+++ b/test/fixtures/aot/cases/strip_tags.phpt
@@ -0,0 +1,11 @@
+--TEST--
+AOT strip_tags()
+--FILE--
+alert(1)hello'), "\n";
+echo strip_tags('xy', ''), "\n";
+echo strip_tags('ab'), "\n";
+--EXPECT--
+alert(1)hello
+xy
+ab