diff --git a/bin/compile.php b/bin/compile.php index 5b3fc61998f..238fdef8e14 100755 --- a/bin/compile.php +++ b/bin/compile.php @@ -127,6 +127,7 @@ function run(string $filename, string $code, array $options): void $debugFile = true === $options['-y'] ? $options['-o'] : $options['-y']; $runtime->setDebug($debugFile); } + require_once __DIR__.'/../lib/AOT/LinkerProcessPolyfill.php'; $runtime->standalone($block, $options['-o']); } } diff --git a/ext/standard/JitEscapeshellarg.php b/ext/standard/JitEscapeshellarg.php new file mode 100644 index 00000000000..53a75a7813e --- /dev/null +++ b/ext/standard/JitEscapeshellarg.php @@ -0,0 +1,46 @@ +builder->call( + $context->lookupFunction('__compiler_escapeshellarg'), + $argStr + ); + $strPtr = $context->getTypeFromString('__string__*'); + $failed = $context->builder->icmp(Builder::INT_EQ, $quoted, $strPtr->constNull()); + + $slot = JitValueBox::alloc($context); + $ptr = JitValueBox::pointer($context, $slot); + $failBlock = BasicBlockHelper::append($context, 'escapeshellarg_fail'); + $okBlock = BasicBlockHelper::append($context, 'escapeshellarg_ok'); + $doneBlock = BasicBlockHelper::append($context, 'escapeshellarg_done'); + $context->builder->branchIf($failed, $failBlock, $okBlock); + + $context->builder->positionAtEnd($failBlock); + $context->builder->call($context->lookupFunction('__value__writeNull'), $ptr); + $context->builder->branch($doneBlock); + + $context->builder->positionAtEnd($okBlock); + $owned = $context->builder->call($context->lookupFunction('__string__separate'), $quoted); + $context->builder->call($context->lookupFunction('__value__writeString'), $ptr, $owned); + $context->builder->branch($doneBlock); + + $context->builder->positionAtEnd($doneBlock); + + return $ptr; + } +} diff --git a/ext/standard/JitPhpcRunCommand.php b/ext/standard/JitPhpcRunCommand.php new file mode 100644 index 00000000000..7f63e161439 --- /dev/null +++ b/ext/standard/JitPhpcRunCommand.php @@ -0,0 +1,72 @@ +getTypeFromString('__hashtable__*'); + $envHt = $htPtrTy->constNull(); + if (null !== $envArg) { + $envHt = self::loadArrayArg($context, $envArg, 2); + } + $resultHt = $context->builder->call( + $context->lookupFunction('__compiler_phpc_run_command'), + $cmdStr, + $envHt + ); + $failed = $context->builder->icmp(Builder::INT_EQ, $resultHt, $htPtrTy->constNull()); + + $slot = JitValueBox::alloc($context); + $ptr = JitValueBox::pointer($context, $slot); + $failBlock = BasicBlockHelper::append($context, 'phpc_run_command_fail'); + $okBlock = BasicBlockHelper::append($context, 'phpc_run_command_ok'); + $doneBlock = BasicBlockHelper::append($context, 'phpc_run_command_done'); + $context->builder->branchIf($failed, $failBlock, $okBlock); + + $context->builder->positionAtEnd($failBlock); + $context->builder->call($context->lookupFunction('__value__writeNull'), $ptr); + $context->builder->branch($doneBlock); + + $context->builder->positionAtEnd($okBlock); + $context->builder->call($context->lookupFunction('__value__writeHashtable'), $ptr, $resultHt); + $context->builder->branch($doneBlock); + + $context->builder->positionAtEnd($doneBlock); + + return $ptr; + } + + private static function loadArrayArg(Context $context, JITVariable $arg, int $position): Value + { + if (JITVariable::TYPE_HASHTABLE === $arg->type) { + return $context->helper->loadValue($arg); + } + if (0 !== ($arg->type & JITVariable::IS_NATIVE_ARRAY)) { + return HashTableHelper::materializeNativeArrayForCall($context, $arg); + } + if (JITVariable::TYPE_VALUE === $arg->type) { + return $context->builder->call( + $context->lookupFunction('__value__readHashtable'), + JitValueBox::pointer($context, $arg->value) + ); + } + + throw new \LogicException( + "phpc_run_command() argument #{$position} must be an array in this compiler build" + ); + } +} diff --git a/ext/standard/Module.php b/ext/standard/Module.php index 1997e7d6b0b..6181a9b4e8b 100755 --- a/ext/standard/Module.php +++ b/ext/standard/Module.php @@ -278,6 +278,8 @@ public function getFunctions(): array new fclose(), new getenv_(), new shell_exec(), + new escapeshellarg(), + new phpc_run_command(), new sys_get_temp_dir(), new tempnam(), new getcwd_(), diff --git a/ext/standard/escapeshellarg.php b/ext/standard/escapeshellarg.php new file mode 100644 index 00000000000..48f5943d255 --- /dev/null +++ b/ext/standard/escapeshellarg.php @@ -0,0 +1,49 @@ +calledArgs)) { + throw new \LogicException('escapeshellarg() requires exactly one argument'); + } + $v = $frame->calledArgs[0]->resolveIndirect(); + if (null === $frame->returnVar) { + return; + } + if (Variable::TYPE_STRING !== $v->type) { + throw new \LogicException('escapeshellarg() requires a string argument in this compiler build'); + } + $frame->returnVar->string(\escapeshellarg($v->toString())); + } + + public function call(Context $context, JITVariable ...$args): Value + { + if (1 !== \count($args)) { + throw new \LogicException('escapeshellarg() requires exactly one argument'); + } + + return JitEscapeshellarg::invoke( + $context, + JitStringArg::lower($context, $args[0], 'escapeshellarg() argument') + ); + } +} diff --git a/ext/standard/phpc_run_command.php b/ext/standard/phpc_run_command.php new file mode 100644 index 00000000000..040e7e745da --- /dev/null +++ b/ext/standard/phpc_run_command.php @@ -0,0 +1,96 @@ +calledArgs); + if ($argc < 1 || $argc > 2) { + throw new \LogicException('phpc_run_command() requires one or two arguments'); + } + if (null === $frame->returnVar) { + return; + } + $cmdVar = $frame->calledArgs[0]->resolveIndirect(); + if (Variable::TYPE_STRING !== $cmdVar->type) { + throw new \LogicException('phpc_run_command() command must be a string'); + } + $env = null; + if (2 === $argc) { + $envVar = $frame->calledArgs[1]->resolveIndirect(); + if (Variable::TYPE_ARRAY !== $envVar->type) { + throw new \LogicException('phpc_run_command() env must be an array'); + } + $env = []; + foreach ($envVar->toArray()->iterateKeyed(true) as $pair) { + [$keyVar, $valVar] = $pair; + if (Variable::TYPE_STRING !== $keyVar->type || Variable::TYPE_STRING !== $valVar->type) { + continue; + } + $env[$keyVar->toString()] = $valVar->toString(); + } + } + $descriptor = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $proc = \proc_open($cmdVar->toString(), $descriptor, $pipes, null, $env); + if (!\is_resource($proc)) { + $frame->returnVar->null(); + + return; + } + \fclose($pipes[0]); + $stdout = \stream_get_contents($pipes[1]); + $stderr = \stream_get_contents($pipes[2]); + \fclose($pipes[1]); + \fclose($pipes[2]); + $code = \proc_close($proc); + $ht = new HashTable(); + $codeVar = new Variable(); + $codeVar->int((int) $code); + $ht->add('code', $codeVar); + $stdoutVar = new Variable(); + $stdoutVar->string(false === $stdout ? '' : $stdout); + $ht->add('stdout', $stdoutVar); + $stderrVar = new Variable(); + $stderrVar->string(false === $stderr ? '' : $stderr); + $ht->add('stderr', $stderrVar); + $frame->returnVar->array($ht); + } + + public function call(Context $context, JITVariable ...$args): Value + { + if (\count($args) < 1 || \count($args) > 2) { + throw new \LogicException('phpc_run_command() requires one or two arguments'); + } + + $envArg = 2 === \count($args) ? $args[1] : null; + + return JitPhpcRunCommand::invoke( + $context, + JitStringArg::lower($context, $args[0], 'phpc_run_command() command'), + $envArg + ); + } +} diff --git a/lib/AOT/Linker.php b/lib/AOT/Linker.php index a2f3c68248e..4f1b53acca1 100644 --- a/lib/AOT/Linker.php +++ b/lib/AOT/Linker.php @@ -155,21 +155,11 @@ private static function compileRuntimeObjects(string $objectFile): array $sourceFlags = self::runtimeCIncludeFlagsFor(basename($source)); $cmd = escapeshellarg($compiler).' -c -fPIC -O2'.$sourceFlags.' ' .escapeshellarg($source).' -o '.escapeshellarg($runtimeObject); - $descriptor = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - $proc = proc_open($cmd, $descriptor, $pipes, null, $env); - if (!is_resource($proc)) { - throw new \LogicException('Failed to start AOT runtime compiler: '.$cmd); - } - fclose($pipes[0]); - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); - fclose($pipes[1]); - fclose($pipes[2]); - $code = proc_close($proc); - if (0 !== $code || !is_file($runtimeObject)) { + $captured = self::runCaptured($cmd, $env); + if (0 !== $captured['code'] || !is_file($runtimeObject)) { throw new \LogicException( 'Failed to compile AOT runtime: '.trim( - ($stderr !== false ? $stderr : '')."\n".($stdout !== false ? $stdout : '') + $captured['stderr']."\n".$captured['stdout'] ) ); } @@ -393,16 +383,8 @@ private static function linkWithSystemCompiler( } $cmd = escapeshellarg($path) . ' ' . $objects . ' -lm '.self::RUNTIME_LINK_LIBS.' -o ' . escapeshellarg($executable); - $descriptor = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - $proc = proc_open($cmd, $descriptor, $pipes, null, null); - if (!is_resource($proc)) { - continue; - } - fclose($pipes[0]); - fclose($pipes[1]); - fclose($pipes[2]); - $code = proc_close($proc); - if (0 === $code) { + $captured = self::runCaptured($cmd, null); + if (0 === $captured['code']) { self::unlinkIfTemp($runtimeObjects); return; @@ -416,23 +398,31 @@ private static function linkWithSystemCompiler( private static function run(string $command, array $env): void { - $descriptor = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - $proc = proc_open($command, $descriptor, $pipes, null, $env); - if (!is_resource($proc)) { - throw new \LogicException('Failed to start linker: ' . $command); - } - fclose($pipes[0]); - $stderr = stream_get_contents($pipes[2]); - fclose($pipes[1]); - fclose($pipes[2]); - $code = proc_close($proc); - if (0 !== $code) { + $captured = self::runCaptured($command, $env); + if (0 !== $captured['code']) { throw new \LogicException( - 'Linking failed (exit ' . $code . '): ' . trim($stderr !== false ? $stderr : '') + 'Linking failed (exit '.$captured['code'].'): '.trim($captured['stderr']) ); } } + /** + * @return array{code:int,stdout:string,stderr:string} + */ + private static function runCaptured(string $command, ?array $env): array + { + $raw = null === $env ? \phpc_run_command($command) : \phpc_run_command($command, $env); + if (!\is_array($raw)) { + return ['code' => 127, 'stdout' => '', 'stderr' => '']; + } + + return [ + 'code' => (int) ($raw['code'] ?? 127), + 'stdout' => \is_string($raw['stdout'] ?? null) ? $raw['stdout'] : '', + 'stderr' => \is_string($raw['stderr'] ?? null) ? $raw['stderr'] : '', + ]; + } + /** * Repo-relative paths to committed M5 vendor prelink objects (issue #1416). * diff --git a/lib/AOT/LinkerProcessPolyfill.php b/lib/AOT/LinkerProcessPolyfill.php new file mode 100644 index 00000000000..d3350402f1d --- /dev/null +++ b/lib/AOT/LinkerProcessPolyfill.php @@ -0,0 +1,57 @@ +|null $env + * + * @return array{code:int,stdout:string,stderr:string}|null + */ + public static function run(string $command, ?array $env = null): ?array + { + $descriptor = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $proc = \proc_open($command, $descriptor, $pipes, null, $env); + if (!\is_resource($proc)) { + return null; + } + \fclose($pipes[0]); + $stdout = \stream_get_contents($pipes[1]); + $stderr = \stream_get_contents($pipes[2]); + \fclose($pipes[1]); + \fclose($pipes[2]); + $code = \proc_close($proc); + + return [ + 'code' => (int) $code, + 'stdout' => false === $stdout ? '' : $stdout, + 'stderr' => false === $stderr ? '' : $stderr, + ]; + } +} + +} + +namespace { + + if (!\function_exists('phpc_run_command')) { + /** + * @param array|null $env + * + * @return array{code:int,stdout:string,stderr:string}|null + */ + function phpc_run_command(string $command, ?array $env = null): ?array + { + return \PHPCompiler\AOT\LinkerProcessPolyfill::run($command, $env); + } + } + +} diff --git a/lib/AOT/runtime/phpc_process.c b/lib/AOT/runtime/phpc_process.c index 80a2d74166b..8c1403a6032 100644 --- a/lib/AOT/runtime/phpc_process.c +++ b/lib/AOT/runtime/phpc_process.c @@ -1,26 +1,76 @@ /* - * Process helpers for AOT/JIT (inventory blocker batch 2). + * Process helpers for AOT/JIT (inventory blocker batch 2, #2779 Linker floor). */ +#include #include #include #include +#include +#include +#include typedef struct __string__ __string__; +typedef struct __ref__ { + void *vtable; + int32_t refcount; + int32_t typeinfo; +} __ref__; + +typedef struct __value__ { + int8_t type; + int8_t value[8]; +} __value__; + +typedef struct __strkey_node__ { + __ref__ ref; + __string__ *key; + __value__ value; + struct __strkey_node__ *next; +} __strkey_node__; + +typedef struct __hashtable__ { + __ref__ ref; + size_t numElements; + size_t nextFreeElement; + size_t capacity; + __value__ *values; + __strkey_node__ *strKeys; + void *objKeys; +} __hashtable__; + extern __string__ *__string__init(long long size, const char *value); +extern __hashtable__ *__hashtable__alloc(void); +extern void __hashtable__setStringKeyString(__hashtable__ *ht, __string__ *key, __string__ *value); +extern void __hashtable__setStringKeyLong(__hashtable__ *ht, __string__ *key, long long value); + +#define PHPC_TYPE_STRING 4 + +static size_t phpc_strlen(__string__ *s) +{ + if (NULL == s) { + return 0; + } + + return (size_t) *((long long *) ((char *) s + sizeof(void *))); +} static const char *phpc_strdata(__string__ *s) { if (NULL == s) { return ""; } + return (const char *) s + sizeof(void *) + sizeof(long long); } -__string__ *__compiler_shell_exec(__string__ *cmd) +static __string__ *phpc_cstr_to_string(const char *cstr) +{ + return __string__init((long long) strlen(cstr), cstr); +} + +static __string__ *phpc_read_stream_all(FILE *fp) { - const char *command; - FILE *fp; char chunk[4096]; char *buf; size_t len; @@ -28,22 +78,10 @@ __string__ *__compiler_shell_exec(__string__ *cmd) char *grown; __string__ *result; - if (NULL == cmd) { - return NULL; - } - command = phpc_strdata(cmd); - if ('\0' == *command) { - return NULL; - } - fp = popen(command, "r"); - if (NULL == fp) { - return NULL; - } cap = 4096; len = 0; buf = (char *) malloc(cap); if (NULL == buf) { - pclose(fp); return NULL; } while (NULL != fgets(chunk, (int) sizeof(chunk), fp)) { @@ -53,7 +91,7 @@ __string__ *__compiler_shell_exec(__string__ *cmd) grown = (char *) realloc(buf, cap); if (NULL == grown) { free(buf); - pclose(fp); + return NULL; } buf = grown; @@ -61,11 +99,213 @@ __string__ *__compiler_shell_exec(__string__ *cmd) memcpy(buf + len, chunk, chunk_len); len += chunk_len; } - if (-1 == pclose(fp) && 0 == len) { + if (0 == len) { free(buf); - return NULL; + + return __string__init(0, ""); } result = __string__init((long long) len, buf); free(buf); + + return result; +} + +__string__ *__compiler_shell_exec(__string__ *cmd) +{ + const char *command; + FILE *fp; + __string__ *result; + + if (NULL == cmd) { + return NULL; + } + command = phpc_strdata(cmd); + if ('\0' == *command) { + return NULL; + } + fp = popen(command, "r"); + if (NULL == fp) { + return NULL; + } + result = phpc_read_stream_all(fp); + if (-1 == pclose(fp) && (NULL == result || 0 == phpc_strlen(result))) { + return NULL; + } + + return result; +} + +__string__ *__compiler_escapeshellarg(__string__ *arg) +{ + const char *src; + size_t src_len; + size_t out_cap; + size_t out_len; + char *out; + char *grown; + __string__ *result; + + if (NULL == arg) { + return __string__init(0, "''"); + } + src = phpc_strdata(arg); + src_len = phpc_strlen(arg); + out_cap = src_len * 4 + 3; + out = (char *) malloc(out_cap); + if (NULL == out) { + return NULL; + } + out_len = 0; + out[out_len++] = '\''; + for (size_t i = 0; i < src_len; i++) { + if ('\'' == src[i]) { + if (out_len + 4 >= out_cap) { + out_cap = (out_len + 4) * 2; + grown = (char *) realloc(out, out_cap); + if (NULL == grown) { + free(out); + + return NULL; + } + out = grown; + } + out[out_len++] = '\''; + out[out_len++] = '\\'; + out[out_len++] = '\''; + out[out_len++] = '\''; + } else { + if (out_len + 1 >= out_cap) { + out_cap = (out_len + 1) * 2; + grown = (char *) realloc(out, out_cap); + if (NULL == grown) { + free(out); + + return NULL; + } + out = grown; + } + out[out_len++] = src[i]; + } + } + if (out_len + 1 >= out_cap) { + out_cap = out_len + 2; + grown = (char *) realloc(out, out_cap); + if (NULL == grown) { + free(out); + + return NULL; + } + out = grown; + } + out[out_len++] = '\''; + result = __string__init((long long) out_len, out); + free(out); + + return result; +} + +static void phpc_apply_env_hashtable(__hashtable__ *env) +{ + __strkey_node__ *node; + + if (NULL == env) { + return; + } + for (node = env->strKeys; NULL != node; node = node->next) { + __string__ *val; + + if ((node->value.type & 0x7f) != PHPC_TYPE_STRING) { + continue; + } + val = *((__string__ **) node->value.value); + if (NULL == node->key || NULL == val) { + continue; + } + setenv(phpc_strdata(node->key), phpc_strdata(val), 1); + } +} + +__hashtable__ *__compiler_phpc_run_command(__string__ *cmd, __hashtable__ *env) +{ + const char *command; + int stdout_pipe[2]; + int stderr_pipe[2]; + pid_t pid; + __hashtable__ *result; + __string__ *stdout_str; + __string__ *stderr_str; + int status; + int exit_code; + + if (NULL == cmd) { + return NULL; + } + command = phpc_strdata(cmd); + if ('\0' == *command) { + return NULL; + } + if (0 != pipe(stdout_pipe) || 0 != pipe(stderr_pipe)) { + return NULL; + } + pid = fork(); + if (-1 == pid) { + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(stderr_pipe[0]); + close(stderr_pipe[1]); + + return NULL; + } + if (0 == pid) { + close(stdout_pipe[0]); + close(stderr_pipe[0]); + dup2(stdout_pipe[1], STDOUT_FILENO); + dup2(stderr_pipe[1], STDERR_FILENO); + close(stdout_pipe[1]); + close(stderr_pipe[1]); + phpc_apply_env_hashtable(env); + execl("/bin/sh", "sh", "-c", command, (char *) NULL); + _exit(127); + } + close(stdout_pipe[1]); + close(stderr_pipe[1]); + { + FILE *stdout_fp = fdopen(stdout_pipe[0], "r"); + FILE *stderr_fp = fdopen(stderr_pipe[0], "r"); + + stdout_str = stdout_fp ? phpc_read_stream_all(stdout_fp) : NULL; + stderr_str = stderr_fp ? phpc_read_stream_all(stderr_fp) : NULL; + if (NULL != stdout_fp) { + fclose(stdout_fp); + } else { + close(stdout_pipe[0]); + } + if (NULL != stderr_fp) { + fclose(stderr_fp); + } else { + close(stderr_pipe[0]); + } + } + if (NULL == stdout_str) { + stdout_str = __string__init(0, ""); + } + if (NULL == stderr_str) { + stderr_str = __string__init(0, ""); + } + if (-1 == waitpid(pid, &status, 0)) { + exit_code = 127; + } else if (WIFEXITED(status)) { + exit_code = WEXITSTATUS(status); + } else { + exit_code = 127; + } + result = __hashtable__alloc(); + if (NULL == result) { + return NULL; + } + __hashtable__setStringKeyLong(result, phpc_cstr_to_string("code"), (long long) exit_code); + __hashtable__setStringKeyString(result, phpc_cstr_to_string("stdout"), stdout_str); + __hashtable__setStringKeyString(result, phpc_cstr_to_string("stderr"), stderr_str); + return result; } diff --git a/lib/JIT/Builtin/Type.php b/lib/JIT/Builtin/Type.php index 67f28e988c1..a943615b3a3 100755 --- a/lib/JIT/Builtin/Type.php +++ b/lib/JIT/Builtin/Type.php @@ -577,6 +577,16 @@ public function register(): void { $fntypeShellExec = $this->context->context->functionType($strPtr, false, $strPtr); $fnShellExec = $this->context->module->addFunction('__compiler_shell_exec', $fntypeShellExec); $this->context->registerFunction('__compiler_shell_exec', $fnShellExec); + $fnEscapeshellarg = $this->context->module->addFunction('__compiler_escapeshellarg', $fntypeShellExec); + $this->context->registerFunction('__compiler_escapeshellarg', $fnEscapeshellarg); + $fntypePhpcRunCommand = $this->context->context->functionType( + $this->context->getTypeFromString('__hashtable__*'), + false, + $strPtr, + $this->context->getTypeFromString('__hashtable__*') + ); + $fnPhpcRunCommand = $this->context->module->addFunction('__compiler_phpc_run_command', $fntypePhpcRunCommand); + $this->context->registerFunction('__compiler_phpc_run_command', $fnPhpcRunCommand); $fntypeSysGetTempDir = $this->context->context->functionType($strPtr, false); $fnSysGetTempDir = $this->context->module->addFunction('__compiler_sys_get_temp_dir', $fntypeSysGetTempDir); $this->context->registerFunction('__compiler_sys_get_temp_dir', $fnSysGetTempDir); diff --git a/lib/JIT/SelfHostBuiltinPolicy.php b/lib/JIT/SelfHostBuiltinPolicy.php index e843ef3ed73..9e04d53f021 100644 --- a/lib/JIT/SelfHostBuiltinPolicy.php +++ b/lib/JIT/SelfHostBuiltinPolicy.php @@ -90,6 +90,8 @@ final class SelfHostBuiltinPolicy private const CATEGORY_PROCESS = [ // Required for AOT linker/toolchain discovery (lib/AOT/Linker.php) and bootstrap M5 path. 'shell_exec' => 'process', + 'escapeshellarg' => 'process', + 'phpc_run_command' => 'process', ]; /** @var array */ diff --git a/test/bootstrap-aot/linker_process_smoke.php b/test/bootstrap-aot/linker_process_smoke.php new file mode 100644 index 00000000000..7d86dad842b --- /dev/null +++ b/test/bootstrap-aot/linker_process_smoke.php @@ -0,0 +1,31 @@ +/dev/null')); + if ('' === $path) { + return 2; + } + $captured = phpc_run_command('echo linker-process-smoke'); + if (!is_array($captured)) { + return 3; + } + if (0 !== (int) ($captured['code'] ?? 1)) { + return 4; + } + $stdout = $captured['stdout'] ?? ''; + if (!is_string($stdout) || !str_contains($stdout, 'linker-process-smoke')) { + return 5; + } + + return 0; +} + +exit(bootstrap_linker_process_smoke()); diff --git a/test/selfhost/compiler_lib_spine_smoke/main.php b/test/selfhost/compiler_lib_spine_smoke/main.php index 4ce84345e9f..eb88906bf8f 100644 --- a/test/selfhost/compiler_lib_spine_smoke/main.php +++ b/test/selfhost/compiler_lib_spine_smoke/main.php @@ -552,6 +552,10 @@ require_once __DIR__.'/../../../ext/standard/setcookie.php'; require_once __DIR__.'/../../../ext/standard/setrawcookie.php'; require_once __DIR__.'/../../../ext/standard/shell_exec.php'; +require_once __DIR__.'/../../../ext/standard/escapeshellarg.php'; +require_once __DIR__.'/../../../ext/standard/phpc_run_command.php'; +require_once __DIR__.'/../../../ext/standard/JitEscapeshellarg.php'; +require_once __DIR__.'/../../../ext/standard/JitPhpcRunCommand.php'; require_once __DIR__.'/../../../ext/standard/sin.php'; require_once __DIR__.'/../../../ext/standard/sleep.php'; require_once __DIR__.'/../../../ext/standard/sort_.php'; diff --git a/test/unit/InventoryBlockerLoweringTest.php b/test/unit/InventoryBlockerLoweringTest.php index e7226723f14..6641994994f 100644 --- a/test/unit/InventoryBlockerLoweringTest.php +++ b/test/unit/InventoryBlockerLoweringTest.php @@ -7,6 +7,7 @@ public function testAssignRefLint(): void { $this->assertSame(0, $this->lint('te public function testGlobalVarLint(): void { $this->assertSame(0, $this->lint('test/bootstrap-aot/global_var_link.php')); } public function testValueBitwiseShiftLint(): void { $this->assertSame(0, $this->lint('test/bootstrap-aot/value_bitwise_shift.php')); } public function testShellExecLint(): void { $this->assertSame(0, $this->lint('test/bootstrap-aot/shell_exec_echo.php')); } + public function testLinkerProcessLint(): void { $this->assertSame(0, $this->lint('test/bootstrap-aot/linker_process_smoke.php')); } public function testShiftRightLint(): void { $this->assertSame(0, $this->lint('test/bootstrap-aot/shift_right.php')); } public function testTryCatchLint(): void { $this->assertSame(0, $this->lint('test/bootstrap-aot/try_catch.php')); } private function lint(string $rel): int { diff --git a/test/unit/SelfHostBuiltinPolicyTest.php b/test/unit/SelfHostBuiltinPolicyTest.php index b92fdca16ed..4702605f26a 100644 --- a/test/unit/SelfHostBuiltinPolicyTest.php +++ b/test/unit/SelfHostBuiltinPolicyTest.php @@ -69,6 +69,8 @@ public function testWave12ArrayOpsUseRealLoweringUnderSelfHostAot(): void 'ini_set', 'ini_get', 'shell_exec', + 'escapeshellarg', + 'phpc_run_command', 'gettype', 'var_export', ] as $fn) {