Upgrade framework
This commit is contained in:
62
vendor/psy/psysh/src/ExecutionLoop/AbstractListener.php
vendored
Normal file
62
vendor/psy/psysh/src/ExecutionLoop/AbstractListener.php
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2022 Justin Hileman
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Psy\ExecutionLoop;
|
||||
|
||||
use Psy\Shell;
|
||||
|
||||
/**
|
||||
* Abstract Execution Loop Listener class.
|
||||
*/
|
||||
abstract class AbstractListener implements Listener
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function beforeRun(Shell $shell)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function beforeLoop(Shell $shell)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onInput(Shell $shell, string $input)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onExecute(Shell $shell, string $code)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterLoop(Shell $shell)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterRun(Shell $shell)
|
||||
{
|
||||
}
|
||||
}
|
||||
83
vendor/psy/psysh/src/ExecutionLoop/Listener.php
vendored
Normal file
83
vendor/psy/psysh/src/ExecutionLoop/Listener.php
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2022 Justin Hileman
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Psy\ExecutionLoop;
|
||||
|
||||
use Psy\Shell;
|
||||
|
||||
/**
|
||||
* Execution Loop Listener interface.
|
||||
*/
|
||||
interface Listener
|
||||
{
|
||||
/**
|
||||
* Determines whether this listener should be active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupported(): bool;
|
||||
|
||||
/**
|
||||
* Called once before the REPL session starts.
|
||||
*
|
||||
* @param Shell $shell
|
||||
*/
|
||||
public function beforeRun(Shell $shell);
|
||||
|
||||
/**
|
||||
* Called at the start of each loop.
|
||||
*
|
||||
* @param Shell $shell
|
||||
*/
|
||||
public function beforeLoop(Shell $shell);
|
||||
|
||||
/**
|
||||
* Called on user input.
|
||||
*
|
||||
* Return a new string to override or rewrite user input.
|
||||
*
|
||||
* @param Shell $shell
|
||||
* @param string $input
|
||||
*
|
||||
* @return string|null User input override
|
||||
*/
|
||||
public function onInput(Shell $shell, string $input);
|
||||
|
||||
/**
|
||||
* Called before executing user code.
|
||||
*
|
||||
* Return a new string to override or rewrite user code.
|
||||
*
|
||||
* Note that this is run *after* the Code Cleaner, so if you return invalid
|
||||
* or unsafe PHP here, it'll be executed without any of the safety Code
|
||||
* Cleaner provides. This comes with the big kid warranty :)
|
||||
*
|
||||
* @param Shell $shell
|
||||
* @param string $code
|
||||
*
|
||||
* @return string|null User code override
|
||||
*/
|
||||
public function onExecute(Shell $shell, string $code);
|
||||
|
||||
/**
|
||||
* Called at the end of each loop.
|
||||
*
|
||||
* @param Shell $shell
|
||||
*/
|
||||
public function afterLoop(Shell $shell);
|
||||
|
||||
/**
|
||||
* Called once after the REPL session ends.
|
||||
*
|
||||
* @param Shell $shell
|
||||
*/
|
||||
public function afterRun(Shell $shell);
|
||||
}
|
||||
289
vendor/psy/psysh/src/ExecutionLoop/ProcessForker.php
vendored
Normal file
289
vendor/psy/psysh/src/ExecutionLoop/ProcessForker.php
vendored
Normal file
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2022 Justin Hileman
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Psy\ExecutionLoop;
|
||||
|
||||
use Psy\Context;
|
||||
use Psy\Exception\BreakException;
|
||||
use Psy\Shell;
|
||||
|
||||
/**
|
||||
* An execution loop listener that forks the process before executing code.
|
||||
*
|
||||
* This is awesome, as the session won't die prematurely if user input includes
|
||||
* a fatal error, such as redeclaring a class or function.
|
||||
*/
|
||||
class ProcessForker extends AbstractListener
|
||||
{
|
||||
private $savegame;
|
||||
private $up;
|
||||
|
||||
private static $pcntlFunctions = [
|
||||
'pcntl_fork',
|
||||
'pcntl_signal_dispatch',
|
||||
'pcntl_signal',
|
||||
'pcntl_waitpid',
|
||||
'pcntl_wexitstatus',
|
||||
];
|
||||
|
||||
private static $posixFunctions = [
|
||||
'posix_getpid',
|
||||
'posix_kill',
|
||||
];
|
||||
|
||||
/**
|
||||
* Process forker is supported if pcntl and posix extensions are available.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupported(): bool
|
||||
{
|
||||
return self::isPcntlSupported() && !self::disabledPcntlFunctions() && self::isPosixSupported() && !self::disabledPosixFunctions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that all required pcntl functions are, in fact, available.
|
||||
*/
|
||||
public static function isPcntlSupported(): bool
|
||||
{
|
||||
foreach (self::$pcntlFunctions as $func) {
|
||||
if (!\function_exists($func)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether required pcntl functions are disabled.
|
||||
*/
|
||||
public static function disabledPcntlFunctions()
|
||||
{
|
||||
return self::checkDisabledFunctions(self::$pcntlFunctions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that all required posix functions are, in fact, available.
|
||||
*/
|
||||
public static function isPosixSupported(): bool
|
||||
{
|
||||
foreach (self::$posixFunctions as $func) {
|
||||
if (!\function_exists($func)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether required posix functions are disabled.
|
||||
*/
|
||||
public static function disabledPosixFunctions()
|
||||
{
|
||||
return self::checkDisabledFunctions(self::$posixFunctions);
|
||||
}
|
||||
|
||||
private static function checkDisabledFunctions(array $functions): array
|
||||
{
|
||||
return \array_values(\array_intersect($functions, \array_map('strtolower', \array_map('trim', \explode(',', \ini_get('disable_functions'))))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Forks into a main and a loop process.
|
||||
*
|
||||
* The loop process will handle the evaluation of all instructions, then
|
||||
* return its state via a socket upon completion.
|
||||
*
|
||||
* @param Shell $shell
|
||||
*/
|
||||
public function beforeRun(Shell $shell)
|
||||
{
|
||||
list($up, $down) = \stream_socket_pair(\STREAM_PF_UNIX, \STREAM_SOCK_STREAM, \STREAM_IPPROTO_IP);
|
||||
|
||||
if (!$up) {
|
||||
throw new \RuntimeException('Unable to create socket pair');
|
||||
}
|
||||
|
||||
$pid = \pcntl_fork();
|
||||
if ($pid < 0) {
|
||||
throw new \RuntimeException('Unable to start execution loop');
|
||||
} elseif ($pid > 0) {
|
||||
// This is the main thread. We'll just wait for a while.
|
||||
|
||||
// We won't be needing this one.
|
||||
\fclose($up);
|
||||
|
||||
// Wait for a return value from the loop process.
|
||||
$read = [$down];
|
||||
$write = null;
|
||||
$except = null;
|
||||
|
||||
do {
|
||||
$n = @\stream_select($read, $write, $except, null);
|
||||
|
||||
if ($n === 0) {
|
||||
throw new \RuntimeException('Process timed out waiting for execution loop');
|
||||
}
|
||||
|
||||
if ($n === false) {
|
||||
$err = \error_get_last();
|
||||
if (!isset($err['message']) || \stripos($err['message'], 'interrupted system call') === false) {
|
||||
$msg = $err['message'] ?
|
||||
\sprintf('Error waiting for execution loop: %s', $err['message']) :
|
||||
'Error waiting for execution loop';
|
||||
throw new \RuntimeException($msg);
|
||||
}
|
||||
}
|
||||
} while ($n < 1);
|
||||
|
||||
$content = \stream_get_contents($down);
|
||||
\fclose($down);
|
||||
|
||||
if ($content) {
|
||||
$shell->setScopeVariables(@\unserialize($content));
|
||||
}
|
||||
|
||||
throw new BreakException('Exiting main thread');
|
||||
}
|
||||
|
||||
// This is the child process. It's going to do all the work.
|
||||
if (!@\cli_set_process_title('psysh (loop)')) {
|
||||
// Fall back to `setproctitle` if that wasn't succesful.
|
||||
if (\function_exists('setproctitle')) {
|
||||
@\setproctitle('psysh (loop)');
|
||||
}
|
||||
}
|
||||
|
||||
// We won't be needing this one.
|
||||
\fclose($down);
|
||||
|
||||
// Save this; we'll need to close it in `afterRun`
|
||||
$this->up = $up;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a savegame at the start of each loop iteration.
|
||||
*
|
||||
* @param Shell $shell
|
||||
*/
|
||||
public function beforeLoop(Shell $shell)
|
||||
{
|
||||
$this->createSavegame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old savegames at the end of each loop iteration.
|
||||
*
|
||||
* @param Shell $shell
|
||||
*/
|
||||
public function afterLoop(Shell $shell)
|
||||
{
|
||||
// if there's an old savegame hanging around, let's kill it.
|
||||
if (isset($this->savegame)) {
|
||||
\posix_kill($this->savegame, \SIGKILL);
|
||||
\pcntl_signal_dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After the REPL session ends, send the scope variables back up to the main
|
||||
* thread (if this is a child thread).
|
||||
*
|
||||
* @param Shell $shell
|
||||
*/
|
||||
public function afterRun(Shell $shell)
|
||||
{
|
||||
// We're a child thread. Send the scope variables back up to the main thread.
|
||||
if (isset($this->up)) {
|
||||
\fwrite($this->up, $this->serializeReturn($shell->getScopeVariables(false)));
|
||||
\fclose($this->up);
|
||||
|
||||
\posix_kill(\posix_getpid(), \SIGKILL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a savegame fork.
|
||||
*
|
||||
* The savegame contains the current execution state, and can be resumed in
|
||||
* the event that the worker dies unexpectedly (for example, by encountering
|
||||
* a PHP fatal error).
|
||||
*/
|
||||
private function createSavegame()
|
||||
{
|
||||
// the current process will become the savegame
|
||||
$this->savegame = \posix_getpid();
|
||||
|
||||
$pid = \pcntl_fork();
|
||||
if ($pid < 0) {
|
||||
throw new \RuntimeException('Unable to create savegame fork');
|
||||
} elseif ($pid > 0) {
|
||||
// we're the savegame now... let's wait and see what happens
|
||||
\pcntl_waitpid($pid, $status);
|
||||
|
||||
// worker exited cleanly, let's bail
|
||||
if (!\pcntl_wexitstatus($status)) {
|
||||
\posix_kill(\posix_getpid(), \SIGKILL);
|
||||
}
|
||||
|
||||
// worker didn't exit cleanly, we'll need to have another go
|
||||
$this->createSavegame();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize all serializable return values.
|
||||
*
|
||||
* A naïve serialization will run into issues if there is a Closure or
|
||||
* SimpleXMLElement (among other things) in scope when exiting the execution
|
||||
* loop. We'll just ignore these unserializable classes, and serialize what
|
||||
* we can.
|
||||
*
|
||||
* @param array $return
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function serializeReturn(array $return): string
|
||||
{
|
||||
$serializable = [];
|
||||
|
||||
foreach ($return as $key => $value) {
|
||||
// No need to return magic variables
|
||||
if (Context::isSpecialVariableName($key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resources and Closures don't error, but they don't serialize well either.
|
||||
if (\is_resource($value) || $value instanceof \Closure) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\version_compare(\PHP_VERSION, '8.1', '>=') && $value instanceof \UnitEnum) {
|
||||
// Enums defined in the REPL session can't be unserialized.
|
||||
$ref = new \ReflectionObject($value);
|
||||
if (\strpos($ref->getFileName(), ": eval()'d code") !== false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@\serialize($value);
|
||||
$serializable[$key] = $value;
|
||||
} catch (\Throwable $e) {
|
||||
// we'll just ignore this one...
|
||||
}
|
||||
}
|
||||
|
||||
return @\serialize($serializable);
|
||||
}
|
||||
}
|
||||
144
vendor/psy/psysh/src/ExecutionLoop/RunkitReloader.php
vendored
Normal file
144
vendor/psy/psysh/src/ExecutionLoop/RunkitReloader.php
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Psy Shell.
|
||||
*
|
||||
* (c) 2012-2022 Justin Hileman
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Psy\ExecutionLoop;
|
||||
|
||||
use Psy\Exception\ParseErrorException;
|
||||
use Psy\ParserFactory;
|
||||
use Psy\Shell;
|
||||
|
||||
/**
|
||||
* A runkit-based code reloader, which is pretty much magic.
|
||||
*/
|
||||
class RunkitReloader extends AbstractListener
|
||||
{
|
||||
private $parser;
|
||||
private $timestamps = [];
|
||||
|
||||
/**
|
||||
* Only enabled if Runkit is installed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupported(): bool
|
||||
{
|
||||
// runkit_import was removed in runkit7-4.0.0a1
|
||||
return \extension_loaded('runkit') || \extension_loaded('runkit7') && \function_exists('runkit_import');
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Runkit Reloader.
|
||||
*
|
||||
* @todo Pass in Parser Factory instance for dependency injection?
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$parserFactory = new ParserFactory();
|
||||
$this->parser = $parserFactory->createParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload code on input.
|
||||
*
|
||||
* @param Shell $shell
|
||||
* @param string $input
|
||||
*/
|
||||
public function onInput(Shell $shell, string $input)
|
||||
{
|
||||
$this->reload($shell);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look through included files and update anything with a new timestamp.
|
||||
*
|
||||
* @param Shell $shell
|
||||
*/
|
||||
private function reload(Shell $shell)
|
||||
{
|
||||
\clearstatcache();
|
||||
$modified = [];
|
||||
|
||||
foreach (\get_included_files() as $file) {
|
||||
$timestamp = \filemtime($file);
|
||||
|
||||
if (!isset($this->timestamps[$file])) {
|
||||
$this->timestamps[$file] = $timestamp;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->timestamps[$file] === $timestamp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->lintFile($file)) {
|
||||
$msg = \sprintf('Modified file "%s" could not be reloaded', $file);
|
||||
$shell->writeException(new ParseErrorException($msg));
|
||||
continue;
|
||||
}
|
||||
|
||||
$modified[] = $file;
|
||||
$this->timestamps[$file] = $timestamp;
|
||||
}
|
||||
|
||||
// switch (count($modified)) {
|
||||
// case 0:
|
||||
// return;
|
||||
|
||||
// case 1:
|
||||
// printf("Reloading modified file: \"%s\"\n", str_replace(getcwd(), '.', $file));
|
||||
// break;
|
||||
|
||||
// default:
|
||||
// printf("Reloading %d modified files\n", count($modified));
|
||||
// break;
|
||||
// }
|
||||
|
||||
foreach ($modified as $file) {
|
||||
$flags = (
|
||||
RUNKIT_IMPORT_FUNCTIONS |
|
||||
RUNKIT_IMPORT_CLASSES |
|
||||
RUNKIT_IMPORT_CLASS_METHODS |
|
||||
RUNKIT_IMPORT_CLASS_CONSTS |
|
||||
RUNKIT_IMPORT_CLASS_PROPS |
|
||||
RUNKIT_IMPORT_OVERRIDE
|
||||
);
|
||||
|
||||
// these two const cannot be used with RUNKIT_IMPORT_OVERRIDE in runkit7
|
||||
if (\extension_loaded('runkit7')) {
|
||||
$flags &= ~RUNKIT_IMPORT_CLASS_PROPS & ~RUNKIT_IMPORT_CLASS_STATIC_PROPS;
|
||||
runkit7_import($file, $flags);
|
||||
} else {
|
||||
runkit_import($file, $flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this file be re-imported?
|
||||
*
|
||||
* Use PHP-Parser to ensure that the file is valid PHP.
|
||||
*
|
||||
* @param string $file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function lintFile(string $file): bool
|
||||
{
|
||||
// first try to parse it
|
||||
try {
|
||||
$this->parser->parse(\file_get_contents($file));
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user