diff --git a/docs/capabilities.md b/docs/capabilities.md index 674daa8ca89..41bf9345b4c 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -46,7 +46,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `array_product` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `array_push` | yes | yes | yes | standard | doc: VM only; JIT PHPT; AOT PHPT | | `array_rand` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | -| `array_reduce` | yes | yes | yes | standard | callbacks: string user functions + VM closures; php-src-strict invalid callback TypeError (#6679); JIT PHPT | +| `array_reduce` | yes | yes | yes | standard | JIT PHPT | | `array_replace` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `array_replace_recursive` | yes | yes | yes | standard | AOT PHPT | | `array_reverse` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | @@ -80,7 +80,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `bin2hex` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `bindec` | yes | yes | yes | standard | | | `boolval` | yes | yes | yes | standard | AOT PHPT | -| `ceil` | yes | yes | yes | standard | | +| `ceil` | yes | yes | yes | standard | JIT PHPT | | `chdir` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `chgrp` | yes | yes | yes | standard | AOT PHPT | | `chmod` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | @@ -141,7 +141,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `disktotalspace` | yes | yes | yes | standard | | | `dl` | yes | yes | yes | standard | | | `doubleval` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | -| `end` | yes | yes | yes | standard | | +| `end` | yes | yes | yes | standard | JIT PHPT | | `enum_exists` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `error_clear_last` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `error_get_last` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | @@ -177,12 +177,12 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `filesize` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `filetype` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `filter_id` | yes | yes | yes | filter | | -| `filter_input` | yes | yes | yes | standard | AOT PHPT | +| `filter_input` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `filter_list` | yes | yes | yes | filter | | | `filter_var` | yes | yes | yes | filter | AOT PHPT | | `floatval` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `flock` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | -| `floor` | yes | yes | yes | standard | | +| `floor` | yes | yes | yes | standard | JIT PHPT | | `flush` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `fmod` | yes | yes | yes | standard | | | `fnmatch` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | @@ -437,7 +437,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `rewind` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `rewinddir` | yes | yes | yes | standard | JIT PHPT | | `rmdir` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | -| `round` | yes | yes | yes | standard | AOT PHPT | +| `round` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `rsort` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `rtrim` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `scandir` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | @@ -467,7 +467,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `soundex` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `spl_autoload_register` | yes | yes | yes | standard | JIT PHPT | | `sprintf` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | -| `sqrt` | yes | yes | yes | standard | | +| `sqrt` | yes | yes | yes | standard | JIT PHPT | | `sscanf` | yes | yes | yes | standard | AOT PHPT | | `stat` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `str_contains` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | @@ -479,7 +479,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `str_pad` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `str_repeat` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `str_replace` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | -| `str_rot13` | yes | yes | yes | standard | AOT PHPT | +| `str_rot13` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `str_shuffle` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `str_split` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `str_starts_with` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | @@ -491,7 +491,8 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `stream_context_create` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `stream_get_contents` | yes | yes | yes | standard | AOT PHPT | | `stream_get_line` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | -| `stream_is_local` | yes | yes | yes | standard | AOT PHPT | +| `stream_is_local` | yes | yes | yes | standard | | +| `stream_isatty` | yes | yes | yes | standard | | | `stream_set_chunk_size` | yes | yes | yes | standard | AOT PHPT | | `stream_set_read_buffer` | yes | yes | yes | standard | AOT PHPT | | `stream_set_timeout` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | @@ -556,7 +557,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `usleep` | yes | yes | yes | standard | JIT PHPT | | `usort` | yes | yes | yes | standard | callbacks: strcmp JIT; strcasecmp VM; VM closure comparator (#3086, #1210); JIT PHPT | | `utf8_decode` | yes | yes | yes | standard | AOT PHPT | -| `utf8_encode` | yes | yes | yes | standard | AOT PHPT | +| `utf8_encode` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `var_dump` | yes | yes | yes | standard | | | `var_export` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `version_compare` | yes | yes | yes | standard | AOT PHPT | diff --git a/ext/standard/JitStreamIsatty.php b/ext/standard/JitStreamIsatty.php new file mode 100644 index 00000000000..aa8c5a21274 --- /dev/null +++ b/ext/standard/JitStreamIsatty.php @@ -0,0 +1,25 @@ +builder->call( + $context->lookupFunction('__compiler_stream_isatty'), + $handleLong + ); + $i32 = $context->getTypeFromString('int32'); + + return $context->builder->icmp(Builder::INT_EQ, $ret, $i32->constInt(1, false)); + } +} diff --git a/ext/standard/Module.php b/ext/standard/Module.php index d46a0364df1..90cf2b16bf5 100755 --- a/ext/standard/Module.php +++ b/ext/standard/Module.php @@ -423,6 +423,7 @@ public function getFunctions(): array new stream_supports(), new stream_supports_lock(), new stream_is_local(), + new stream_isatty(), new fopen(), new fread(), new stream_get_contents(), diff --git a/ext/standard/VmFs.php b/ext/standard/VmFs.php index f023dcbaa54..2d2f3ca39f9 100644 --- a/ext/standard/VmFs.php +++ b/ext/standard/VmFs.php @@ -686,6 +686,21 @@ public static function streamSetReadBuffer(int $handle, int $buffer) { return (int) $previous; } + /** + * stream_isatty() — php-src ext/standard/streamsfuncs.c (issue #6035). + * + * Returns true when the stream is connected to a terminal (php_stream_isatty). + */ + public static function streamIsatty(int $handle): bool + { + $fp = self::lookup($handle); + if (null === $fp) { + return false; + } + + return \stream_isatty($fp); + } + /** * stream_is_local() — php-src ext/standard/streamsfuncs.c (issue #6173). * diff --git a/ext/standard/stream_isatty.php b/ext/standard/stream_isatty.php new file mode 100644 index 00000000000..265bb75c3ed --- /dev/null +++ b/ext/standard/stream_isatty.php @@ -0,0 +1,51 @@ +calledArgs)) { + throw new \LogicException('stream_isatty() requires exactly one argument in this compiler build'); + } + $handle = VmStreamArg::requireStreamHandle( + $frame->calledArgs[0]->resolveIndirect(), + 'stream_isatty' + ); + if (null === $frame->returnVar) { + return; + } + $frame->returnVar->bool(VmFs::streamIsatty($handle)); + } + + public function call(Context $context, JITVariable ...$args): Value + { + if (1 !== \count($args)) { + throw new \LogicException('stream_isatty() requires exactly one argument in this compiler build'); + } + + return JitStreamIsatty::invoke( + $context, + $context->builder->truncOrBitCast( + JitLongArg::lower($context, $args[0], 'stream_isatty() stream'), + $context->getTypeFromString('int64') + ) + ); + } +} diff --git a/lib/AOT/runtime/phpc_stream.c b/lib/AOT/runtime/phpc_stream.c index 1c55ef6121d..72113a5ad22 100644 --- a/lib/AOT/runtime/phpc_stream.c +++ b/lib/AOT/runtime/phpc_stream.c @@ -425,6 +425,28 @@ int __compiler_stream_is_local(int64_t handle) return phpc_stream_is_url_path(path) ? 0 : 1; } +int __compiler_stream_isatty(int64_t handle) +{ + FILE *fp = __phpc_resolve_stream(handle); + + if (NULL == fp) { + return 0; + } +#if defined(_WIN32) + return _isatty(_fileno(fp)) ? 1 : 0; +#else + { + int fd = fileno(fp); + + if (fd < 0) { + return 0; + } + + return isatty(fd) ? 1 : 0; + } +#endif +} + int __compiler_stream_supports(int64_t handle, int64_t feature) { FILE *fp; diff --git a/lib/JIT/Builtin/Type.php b/lib/JIT/Builtin/Type.php index 9155660c700..0bfe66ecbbc 100755 --- a/lib/JIT/Builtin/Type.php +++ b/lib/JIT/Builtin/Type.php @@ -326,6 +326,9 @@ public function register(): void { $fntypeStreamIsLocal = $this->context->context->functionType($i32, false, $i64); $fnStreamIsLocal = $this->context->module->addFunction('__compiler_stream_is_local', $fntypeStreamIsLocal); $this->context->registerFunction('__compiler_stream_is_local', $fnStreamIsLocal); + $fntypeStreamIsatty = $this->context->context->functionType($i32, false, $i64); + $fnStreamIsatty = $this->context->module->addFunction('__compiler_stream_isatty', $fntypeStreamIsatty); + $this->context->registerFunction('__compiler_stream_isatty', $fnStreamIsatty); $fntypeFtruncate = $this->context->context->functionType($i32, false, $i64, $i64); $fnFtruncate = $this->context->module->addFunction('__compiler_ftruncate', $fntypeFtruncate); $this->context->registerFunction('__compiler_ftruncate', $fnFtruncate); diff --git a/test/compliance/cases/stdlib/stream_isatty.phpt b/test/compliance/cases/stdlib/stream_isatty.phpt new file mode 100644 index 00000000000..b7a0c7b3f5f --- /dev/null +++ b/test/compliance/cases/stdlib/stream_isatty.phpt @@ -0,0 +1,17 @@ +--TEST-- +stream_isatty() on php://memory and plainfile (issue #6035, php-src streamsfuncs.c) +--FILE-- +getMessage(), "\n"; +} +--EXPECT-- +stream_isatty(): Argument #1 ($stream) must be of type resource, E given diff --git a/test/compliance/cases/stdlib/stream_isatty_typeerror.phpt b/test/compliance/cases/stdlib/stream_isatty_typeerror.phpt new file mode 100644 index 00000000000..057d5ac018a --- /dev/null +++ b/test/compliance/cases/stdlib/stream_isatty_typeerror.phpt @@ -0,0 +1,12 @@ +--TEST-- +stream_isatty() non-resource operand TypeError (#6035, php-src streamsfuncs.c) +--FILE-- +getMessage(), "\n"; +} +--EXPECT-- +stream_isatty(): Argument #1 ($stream) must be of type resource, array given diff --git a/test/repro/stream_isatty_6035.php b/test/repro/stream_isatty_6035.php new file mode 100644 index 00000000000..36e00d2af1e --- /dev/null +++ b/test/repro/stream_isatty_6035.php @@ -0,0 +1,5 @@ +