summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Scribunto/includes/engines/LuaStandalone/LuaStandaloneEngine.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/Scribunto/includes/engines/LuaStandalone/LuaStandaloneEngine.php')
-rw-r--r--www/wiki/extensions/Scribunto/includes/engines/LuaStandalone/LuaStandaloneEngine.php781
1 files changed, 781 insertions, 0 deletions
diff --git a/www/wiki/extensions/Scribunto/includes/engines/LuaStandalone/LuaStandaloneEngine.php b/www/wiki/extensions/Scribunto/includes/engines/LuaStandalone/LuaStandaloneEngine.php
new file mode 100644
index 00000000..330a7b9a
--- /dev/null
+++ b/www/wiki/extensions/Scribunto/includes/engines/LuaStandalone/LuaStandaloneEngine.php
@@ -0,0 +1,781 @@
+<?php
+
+use MediaWiki\Logger\LoggerFactory;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
+// @codingStandardsIgnoreLine Squiz.Classes.ValidClassName.NotCamelCaps
+class Scribunto_LuaStandaloneEngine extends Scribunto_LuaEngine {
+ protected static $clockTick;
+ public $initialStatus;
+
+ /**
+ * @var Scribunto_LuaStandaloneInterpreter
+ */
+ protected $interpreter;
+
+ public function load() {
+ parent::load();
+ if ( php_uname( 's' ) === 'Linux' ) {
+ $this->initialStatus = $this->interpreter->getStatus();
+ } else {
+ $this->initialStatus = false;
+ }
+ }
+
+ public function getPerformanceCharacteristics() {
+ return [
+ 'phpCallsRequireSerialization' => true,
+ ];
+ }
+
+ function reportLimitData( ParserOutput $output ) {
+ try {
+ $this->load();
+ } catch ( Exception $e ) {
+ return;
+ }
+ if ( $this->initialStatus ) {
+ $status = $this->interpreter->getStatus();
+ $output->setLimitReportData( 'scribunto-limitreport-timeusage',
+ [
+ sprintf( "%.3f", $status['time'] / $this->getClockTick() ),
+ sprintf( "%.3f", $this->options['cpuLimit'] )
+ ]
+ );
+ $output->setLimitReportData( 'scribunto-limitreport-virtmemusage',
+ [
+ $status['vsize'],
+ $this->options['memoryLimit']
+ ]
+ );
+ $output->setLimitReportData( 'scribunto-limitreport-estmemusage',
+ $status['vsize'] - $this->initialStatus['vsize']
+ );
+ }
+ $logs = $this->getLogBuffer();
+ if ( $logs !== '' ) {
+ $output->addModules( 'ext.scribunto.logs' );
+ $output->setLimitReportData( 'scribunto-limitreport-logs', $logs );
+ }
+ }
+
+ function formatLimitData( $key, &$value, &$report, $isHTML, $localize ) {
+ global $wgLang;
+ $lang = $localize ? $wgLang : Language::factory( 'en' );
+ switch ( $key ) {
+ case 'scribunto-limitreport-logs':
+ if ( $isHTML ) {
+ $report .= $this->formatHtmlLogs( $value, $localize );
+ }
+ return false;
+ case 'scribunto-limitreport-virtmemusage':
+ $value = array_map( [ $lang, 'formatSize' ], $value );
+ break;
+ case 'scribunto-limitreport-estmemusage':
+ /** @suppress PhanTypeMismatchArgument */
+ $value = $lang->formatSize( $value );
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * @return mixed
+ */
+ function getClockTick() {
+ if ( self::$clockTick === null ) {
+ Wikimedia\suppressWarnings();
+ self::$clockTick = intval( shell_exec( 'getconf CLK_TCK' ) );
+ Wikimedia\restoreWarnings();
+ if ( !self::$clockTick ) {
+ self::$clockTick = 100;
+ }
+ }
+ return self::$clockTick;
+ }
+
+ /**
+ * @return Scribunto_LuaStandaloneInterpreter
+ */
+ function newInterpreter() {
+ return new Scribunto_LuaStandaloneInterpreter( $this, $this->options + [
+ 'logger' => LoggerFactory::getInstance( 'Scribunto' )
+ ] );
+ }
+
+ public function getSoftwareInfo( array &$software ) {
+ $ver = Scribunto_LuaStandaloneInterpreter::getLuaVersion( $this->options );
+ if ( $ver !== null ) {
+ if ( substr( $ver, 0, 6 ) === 'LuaJIT' ) {
+ $software['[http://luajit.org/ LuaJIT]'] = str_replace( 'LuaJIT ', '', $ver );
+ } else {
+ $software['[http://www.lua.org/ Lua]'] = str_replace( 'Lua ', '', $ver );
+ }
+ }
+ }
+}
+
+// @codingStandardsIgnoreLine Squiz.Classes.ValidClassName.NotCamelCaps
+class Scribunto_LuaStandaloneInterpreter extends Scribunto_LuaInterpreter {
+ protected static $nextInterpreterId = 0;
+
+ /**
+ * @var Scribunto_LuaStandaloneEngine
+ */
+ public $engine;
+
+ /**
+ * @var bool
+ */
+ public $enableDebug;
+
+ /**
+ * @var resource
+ */
+ public $proc;
+
+ /**
+ * @var resource
+ */
+ public $writePipe;
+
+ /**
+ * @var resource
+ */
+ public $readPipe;
+
+ /**
+ * @var ScribuntoException
+ */
+ public $exitError;
+
+ /**
+ * @var int
+ */
+ public $id;
+
+ /**
+ * @var LoggerInterface
+ */
+ protected $logger;
+
+ /**
+ * @var callable[]
+ */
+ protected $callbacks;
+
+ /**
+ * @param Scribunto_LuaStandaloneEngine $engine
+ * @param array $options
+ * @throws MWException
+ * @throws Scribunto_LuaInterpreterNotFoundError
+ * @throws ScribuntoException
+ */
+ function __construct( $engine, array $options ) {
+ $this->id = self::$nextInterpreterId++;
+
+ if ( $options['errorFile'] === null ) {
+ $options['errorFile'] = wfGetNull();
+ }
+
+ if ( $options['luaPath'] === null ) {
+ $path = false;
+
+ // Note, if you alter these, also alter getLuaVersion() below
+ if ( PHP_OS == 'Linux' ) {
+ if ( PHP_INT_SIZE == 4 ) {
+ $path = 'lua5_1_5_linux_32_generic/lua';
+ } elseif ( PHP_INT_SIZE == 8 ) {
+ $path = 'lua5_1_5_linux_64_generic/lua';
+ }
+ } elseif ( PHP_OS == 'Windows' || PHP_OS == 'WINNT' || PHP_OS == 'Win32' ) {
+ if ( PHP_INT_SIZE == 4 ) {
+ $path = 'lua5_1_5_Win32_bin/lua5.1.exe';
+ } elseif ( PHP_INT_SIZE == 8 ) {
+ $path = 'lua5_1_5_Win64_bin/lua5.1.exe';
+ }
+ } elseif ( PHP_OS == 'Darwin' ) {
+ $path = 'lua5_1_5_mac_lion_fat_generic/lua';
+ }
+ if ( $path === false ) {
+ throw new Scribunto_LuaInterpreterNotFoundError(
+ 'No Lua interpreter was given in the configuration, ' .
+ 'and no bundled binary exists for this platform.' );
+ }
+ $options['luaPath'] = __DIR__ . "/binaries/$path";
+
+ if ( !is_executable( $options['luaPath'] ) ) {
+ throw new MWException(
+ sprintf( 'The lua binary (%s) is not executable.', $options['luaPath'] )
+ );
+ }
+ }
+
+ $this->engine = $engine;
+ $this->enableDebug = !empty( $options['debug'] );
+ $this->logger = isset( $options['logger'] )
+ ? $options['logger']
+ : new NullLogger();
+
+ $pipes = null;
+ $cmd = wfEscapeShellArg(
+ $options['luaPath'],
+ __DIR__ . '/mw_main.lua',
+ dirname( dirname( __DIR__ ) ),
+ $this->id,
+ PHP_INT_SIZE
+ );
+ if ( php_uname( 's' ) == 'Linux' ) {
+ // Limit memory and CPU
+ $cmd = wfEscapeShellArg(
+ 'exec', # proc_open() passes $cmd to 'sh -c' on Linux, so add an 'exec' to bypass it
+ '/bin/sh',
+ __DIR__ . '/lua_ulimit.sh',
+ $options['cpuLimit'], # soft limit (SIGXCPU)
+ $options['cpuLimit'] + 1, # hard limit
+ intval( $options['memoryLimit'] / 1024 ),
+ $cmd );
+ }
+
+ if ( php_uname( 's' ) == 'Windows NT' ) {
+ // Like the passthru() in older versions of PHP,
+ // PHP's invokation of cmd.exe in proc_open() is broken:
+ // http://news.php.net/php.internals/21796
+ // Unlike passthru(), it is not fixed in any PHP version,
+ // so we use the fix similar to one in wfShellExec()
+ $cmd = '"' . $cmd . '"';
+ }
+
+ $this->logger->debug( __METHOD__.": creating interpreter: $cmd\n" );
+
+ // Check whether proc_open is available before trying to call it (e.g.
+ // PHP's disable_functions may have removed it)
+ if ( !function_exists( 'proc_open' ) ) {
+ throw $this->engine->newException( 'scribunto-luastandalone-proc-error-proc-open' );
+ }
+
+ // Clear the "last error", so if proc_open fails we can know any
+ // warning was generated by that.
+ Wikimedia\suppressWarnings();
+ trigger_error( '' );
+ Wikimedia\restoreWarnings();
+
+ $this->proc = proc_open(
+ $cmd,
+ [
+ [ 'pipe', 'r' ],
+ [ 'pipe', 'w' ],
+ [ 'file', $options['errorFile'], 'a' ]
+ ],
+ $pipes );
+ if ( !$this->proc ) {
+ $err = error_get_last();
+ if ( !empty( $err['message'] ) ) {
+ throw $this->engine->newException( 'scribunto-luastandalone-proc-error-msg',
+ [ 'args' => [ $err['message'] ] ] );
+ } else {
+ throw $this->engine->newException( 'scribunto-luastandalone-proc-error' );
+ }
+ }
+ $this->writePipe = $pipes[0];
+ $this->readPipe = $pipes[1];
+ }
+
+ function __destruct() {
+ $this->terminate();
+ }
+
+ public static function getLuaVersion( array $options ) {
+ if ( $options['luaPath'] === null ) {
+ // We know which versions are distributed, no need to run them.
+ if ( PHP_OS == 'Linux' ) {
+ return 'Lua 5.1.5';
+ } elseif ( PHP_OS == 'Windows' || PHP_OS == 'WINNT' || PHP_OS == 'Win32' ) {
+ return 'Lua 5.1.4';
+ } elseif ( PHP_OS == 'Darwin' ) {
+ return 'Lua 5.1.5';
+ } else {
+ return null;
+ }
+ }
+
+ // Ask the interpreter what version it is, using the "-v" option.
+ // The output is expected to be one line, something like these:
+ // Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio
+ // LuaJIT 2.0.0 -- Copyright (C) 2005-2012 Mike Pall. http://luajit.org/
+ $cmd = wfEscapeShellArg( $options['luaPath'] ) . ' -v';
+ $handle = popen( $cmd, 'r' );
+ if ( $handle ) {
+ $ret = fgets( $handle, 80 );
+ pclose( $handle );
+ if ( $ret && preg_match( '/^Lua(?:JIT)? \S+/', $ret, $m ) ) {
+ return $m[0];
+ }
+ }
+ return null;
+ }
+
+ public function terminate() {
+ if ( $this->proc ) {
+ $this->logger->debug( __METHOD__.": terminating\n" );
+ proc_terminate( $this->proc );
+ proc_close( $this->proc );
+ $this->proc = false;
+ }
+ }
+
+ public function quit() {
+ if ( !$this->proc ) {
+ return;
+ }
+ $this->dispatch( [ 'op' => 'quit' ] );
+ proc_close( $this->proc );
+ }
+
+ public function testquit() {
+ if ( !$this->proc ) {
+ return;
+ }
+ $this->dispatch( [ 'op' => 'testquit' ] );
+ proc_close( $this->proc );
+ }
+
+ /**
+ * @param string $text
+ * @param string $chunkName
+ * @return Scribunto_LuaStandaloneInterpreterFunction
+ */
+ public function loadString( $text, $chunkName ) {
+ $this->cleanupLuaChunks();
+
+ $result = $this->dispatch( [
+ 'op' => 'loadString',
+ 'text' => $text,
+ 'chunkName' => $chunkName
+ ] );
+ return new Scribunto_LuaStandaloneInterpreterFunction( $this->id, $result[1] );
+ }
+
+ public function callFunction( $func /* ... */ ) {
+ if ( !( $func instanceof Scribunto_LuaStandaloneInterpreterFunction ) ) {
+ throw new MWException( __METHOD__.': invalid function type' );
+ }
+ if ( $func->interpreterId !== $this->id ) {
+ throw new MWException( __METHOD__.': function belongs to a different interpreter' );
+ }
+ $args = func_get_args();
+ unset( $args[0] );
+ // $args is now conveniently a 1-based array, as required by the Lua server
+
+ $this->cleanupLuaChunks();
+
+ $result = $this->dispatch( [
+ 'op' => 'call',
+ 'id' => $func->id,
+ 'nargs' => count( $args ),
+ 'args' => $args,
+ ] );
+ // Convert return values to zero-based
+ return array_values( $result );
+ }
+
+ public function wrapPhpFunction( $callable ) {
+ static $uid = 0;
+ $id = "anonymous*" . ++$uid;
+ $this->callbacks[$id] = $callable;
+ $ret = $this->dispatch( [
+ 'op' => 'wrapPhpFunction',
+ 'id' => $id,
+ ] );
+ return $ret[1];
+ }
+
+ public function cleanupLuaChunks() {
+ if ( isset( Scribunto_LuaStandaloneInterpreterFunction::$anyChunksDestroyed[$this->id] ) ) {
+ unset( Scribunto_LuaStandaloneInterpreterFunction::$anyChunksDestroyed[$this->id] );
+ $this->dispatch( [
+ 'op' => 'cleanupChunks',
+ 'ids' => Scribunto_LuaStandaloneInterpreterFunction::$activeChunkIds[$this->id]
+ ] );
+ }
+ }
+
+ public function isLuaFunction( $object ) {
+ return $object instanceof Scribunto_LuaStandaloneInterpreterFunction;
+ }
+
+ public function registerLibrary( $name, array $functions ) {
+ $functionIds = [];
+ foreach ( $functions as $funcName => $callback ) {
+ $id = "$name-$funcName";
+ $this->callbacks[$id] = $callback;
+ $functionIds[$funcName] = $id;
+ }
+ $this->dispatch( [
+ 'op' => 'registerLibrary',
+ 'name' => $name,
+ 'functions' => $functionIds,
+ ] );
+ }
+
+ public function getStatus() {
+ $result = $this->dispatch( [
+ 'op' => 'getStatus',
+ ] );
+ return $result[1];
+ }
+
+ public function pauseUsageTimer() {
+ }
+
+ public function unpauseUsageTimer() {
+ }
+
+ /**
+ * Fill in missing nulls in a list received from Lua
+ *
+ * @param array $array List received from Lua
+ * @param int $count Number of values that should be in the list
+ * @return array Non-sparse array
+ */
+ private static function fixNulls( array $array, $count ) {
+ if ( count( $array ) === $count ) {
+ return $array;
+ } else {
+ return array_replace( array_fill( 1, $count, null ), $array );
+ }
+ }
+
+ protected function handleCall( $message ) {
+ $message['args'] = self::fixNulls( $message['args'], $message['nargs'] );
+ try {
+ $result = $this->callback( $message['id'], $message['args'] );
+ } catch ( Scribunto_LuaError $e ) {
+ return [
+ 'op' => 'error',
+ 'value' => $e->getLuaMessage(),
+ ];
+ }
+
+ // Convert to a 1-based array
+ if ( $result !== null && count( $result ) ) {
+ $result = array_combine( range( 1, count( $result ) ), $result );
+ } else {
+ $result = [];
+ }
+
+ return [
+ 'op' => 'return',
+ 'nvalues' => count( $result ),
+ 'values' => $result
+ ];
+ }
+
+ protected function callback( $id, array $args ) {
+ return call_user_func_array( $this->callbacks[$id], $args );
+ }
+
+ protected function handleError( $message ) {
+ $opts = [];
+ if ( preg_match( '/^(.*?):(\d+): (.*)$/', $message['value'], $m ) ) {
+ $opts['module'] = $m[1];
+ $opts['line'] = $m[2];
+ $message['value'] = $m[3];
+ }
+ if ( isset( $message['trace'] ) ) {
+ $opts['trace'] = array_values( $message['trace'] );
+ }
+ throw $this->engine->newLuaError( $message['value'], $opts );
+ }
+
+ protected function dispatch( $msgToLua ) {
+ $this->sendMessage( $msgToLua );
+ while ( true ) {
+ $msgFromLua = $this->receiveMessage();
+
+ switch ( $msgFromLua['op'] ) {
+ case 'return':
+ return self::fixNulls( $msgFromLua['values'], $msgFromLua['nvalues'] );
+ case 'call':
+ $msgToLua = $this->handleCall( $msgFromLua );
+ $this->sendMessage( $msgToLua );
+ break;
+ case 'error':
+ $this->handleError( $msgFromLua );
+ return; // not reached
+ default:
+ $this->logger->error( __METHOD__ .": invalid response op \"{$msgFromLua['op']}\"\n" );
+ throw $this->engine->newException( 'scribunto-luastandalone-decode-error' );
+ }
+ }
+ }
+
+ protected function sendMessage( $msg ) {
+ $this->debug( "TX ==> {$msg['op']}" );
+ $this->checkValid();
+ // Send the message
+ $encMsg = $this->encodeMessage( $msg );
+ if ( !fwrite( $this->writePipe, $encMsg ) ) {
+ // Write error, probably the process has terminated
+ // If it has, handleIOError() will throw. If not, throw an exception ourselves.
+ $this->handleIOError();
+ throw $this->engine->newException( 'scribunto-luastandalone-write-error' );
+ }
+ }
+
+ protected function receiveMessage() {
+ $this->checkValid();
+ // Read the header
+ $header = fread( $this->readPipe, 16 );
+ if ( strlen( $header ) !== 16 ) {
+ $this->handleIOError();
+ throw $this->engine->newException( 'scribunto-luastandalone-read-error' );
+ }
+ $length = $this->decodeHeader( $header );
+
+ // Read the reply body
+ $body = '';
+ $lengthRemaining = $length;
+ while ( $lengthRemaining ) {
+ $buffer = fread( $this->readPipe, $lengthRemaining );
+ if ( $buffer === false || feof( $this->readPipe ) ) {
+ $this->handleIOError();
+ throw $this->engine->newException( 'scribunto-luastandalone-read-error' );
+ }
+ $body .= $buffer;
+ $lengthRemaining -= strlen( $buffer );
+ }
+ $body = strtr( $body, [
+ '\\r' => "\r",
+ '\\n' => "\n",
+ '\\\\' => '\\',
+ ] );
+ $msg = unserialize( $body );
+ $this->debug( "RX <== {$msg['op']}" );
+ return $msg;
+ }
+
+ protected function encodeMessage( $message ) {
+ $serialized = $this->encodeLuaVar( $message );
+ $length = strlen( $serialized );
+ $check = $length * 2 - 1;
+
+ return sprintf( '%08x%08x%s', $length, $check, $serialized );
+ }
+
+ /**
+ * @param mixed $var
+ * @param int $level
+ *
+ * @return string
+ * @throws MWException
+ */
+ protected function encodeLuaVar( $var, $level = 0 ) {
+ if ( $level > 100 ) {
+ throw new MWException( __METHOD__.': recursion depth limit exceeded' );
+ }
+ $type = gettype( $var );
+ switch ( $type ) {
+ case 'boolean':
+ return $var ? 'true' : 'false';
+ case 'integer':
+ return $var;
+ case 'double':
+ if ( !is_finite( $var ) ) {
+ if ( is_nan( $var ) ) {
+ return '(0/0)';
+ }
+ if ( $var === INF ) {
+ return '(1/0)';
+ }
+ if ( $var === -INF ) {
+ return '(-1/0)';
+ }
+ throw new MWException( __METHOD__.': cannot convert non-finite number' );
+ }
+ return sprintf( '%.17g', $var );
+ case 'string':
+ return '"' .
+ strtr( $var, [
+ '"' => '\\"',
+ '\\' => '\\\\',
+ "\n" => '\\n',
+ "\r" => '\\r',
+ "\000" => '\\000',
+ ] ) .
+ '"';
+ case 'array':
+ $s = '{';
+ foreach ( $var as $key => $element ) {
+ if ( $s !== '{' ) {
+ $s .= ',';
+ }
+
+ // Lua's number type can't represent most integers beyond 2**53, so stringify such keys
+ if ( is_int( $key ) && ( $key > 9007199254740992 || $key < -9007199254740992 ) ) {
+ $key = sprintf( '%d', $key );
+ }
+
+ $s .= '[' . $this->encodeLuaVar( $key, $level + 1 ) . ']' .
+ '=' . $this->encodeLuaVar( $element, $level + 1 );
+ }
+ $s .= '}';
+ return $s;
+ case 'object':
+ if ( !( $var instanceof Scribunto_LuaStandaloneInterpreterFunction ) ) {
+ throw new MWException( __METHOD__.': unable to convert object of type ' .
+ get_class( $var ) );
+ } elseif ( $var->interpreterId !== $this->id ) {
+ throw new MWException(
+ __METHOD__.': unable to convert function belonging to a different interpreter'
+ );
+ } else {
+ return 'chunks[' . intval( $var->id ) . ']';
+ }
+ case 'resource':
+ throw new MWException( __METHOD__.': unable to convert resource' );
+ case 'NULL':
+ return 'nil';
+ default:
+ throw new MWException( __METHOD__.': unable to convert variable of unknown type' );
+ }
+ }
+
+ protected function decodeHeader( $header ) {
+ $length = substr( $header, 0, 8 );
+ $check = substr( $header, 8, 8 );
+ if ( !preg_match( '/^[0-9a-f]+$/', $length ) || !preg_match( '/^[0-9a-f]+$/', $check ) ) {
+ throw $this->engine->newException( 'scribunto-luastandalone-decode-error' );
+ }
+ $length = hexdec( $length );
+ $check = hexdec( $check );
+ if ( $length * 2 - 1 !== $check ) {
+ throw $this->engine->newException( 'scribunto-luastandalone-decode-error' );
+ }
+ return $length;
+ }
+
+ /**
+ * @throws ScribuntoException
+ */
+ protected function checkValid() {
+ if ( !$this->proc ) {
+ $this->logger->error( __METHOD__ . ": process already terminated\n" );
+ if ( $this->exitError ) {
+ throw $this->exitError;
+ } else {
+ throw $this->engine->newException( 'scribunto-luastandalone-gone' );
+ }
+ }
+ }
+
+ /**
+ * @throws ScribuntoException
+ */
+ protected function handleIOError() {
+ $this->checkValid();
+
+ // Terminate, fetch the status, then close. proc_close()'s return
+ // value isn't helpful here because there's no way to differentiate a
+ // signal-kill from a normal exit.
+ proc_terminate( $this->proc );
+ while ( true ) {
+ $status = proc_get_status( $this->proc );
+ if ( $status === false ) {
+ // WTF? Let the caller throw an appropriate error.
+ return;
+ }
+ if ( !$status['running'] ) {
+ break;
+ }
+ usleep( 10000 ); // Give the killed process a chance to be scheduled
+ }
+ proc_close( $this->proc );
+ $this->proc = false;
+
+ // proc_open() sometimes uses a shell, check for shell-style signal reporting.
+ if ( !$status['signaled'] && ( $status['exitcode'] & 0x80 ) === 0x80 ) {
+ $status['signaled'] = true;
+ $status['termsig'] = $status['exitcode'] - 128;
+ }
+
+ if ( $status['signaled'] ) {
+ if ( defined( 'SIGXCPU' ) && $status['termsig'] === SIGXCPU ) {
+ $this->exitError = $this->engine->newException( 'scribunto-common-timeout' );
+ } else {
+ $this->exitError = $this->engine->newException( 'scribunto-luastandalone-signal',
+ [ 'args' => [ $status['termsig'] ] ] );
+ }
+ } else {
+ $this->exitError = $this->engine->newException( 'scribunto-luastandalone-exited',
+ [ 'args' => [ $status['exitcode'] ] ] );
+ }
+ throw $this->exitError;
+ }
+
+ protected function debug( $msg ) {
+ if ( $this->enableDebug ) {
+ $this->logger->debug( "Lua: $msg\n" );
+ }
+ }
+}
+
+// @codingStandardsIgnoreLine Squiz.Classes.ValidClassName.NotCamelCaps
+class Scribunto_LuaStandaloneInterpreterFunction {
+ public static $anyChunksDestroyed = [];
+ public static $activeChunkIds = [];
+
+ /**
+ * @var int
+ */
+ public $interpreterId;
+
+ /**
+ * @var int
+ */
+ public $id;
+
+ /**
+ * @param int $interpreterId
+ * @param int $id
+ */
+ function __construct( $interpreterId, $id ) {
+ $this->interpreterId = $interpreterId;
+ $this->id = $id;
+ $this->incrementRefCount();
+ }
+
+ function __clone() {
+ $this->incrementRefCount();
+ }
+
+ function __wakeup() {
+ $this->incrementRefCount();
+ }
+
+ function __destruct() {
+ $this->decrementRefCount();
+ }
+
+ private function incrementRefCount() {
+ if ( !isset( self::$activeChunkIds[$this->interpreterId] ) ) {
+ self::$activeChunkIds[$this->interpreterId] = [ $this->id => 1 ];
+ } elseif ( !isset( self::$activeChunkIds[$this->interpreterId][$this->id] ) ) {
+ self::$activeChunkIds[$this->interpreterId][$this->id] = 1;
+ } else {
+ self::$activeChunkIds[$this->interpreterId][$this->id]++;
+ }
+ }
+
+ private function decrementRefCount() {
+ if ( isset( self::$activeChunkIds[$this->interpreterId][$this->id] ) ) {
+ if ( --self::$activeChunkIds[$this->interpreterId][$this->id] <= 0 ) {
+ unset( self::$activeChunkIds[$this->interpreterId][$this->id] );
+ self::$anyChunksDestroyed[$this->interpreterId] = true;
+ }
+ } else {
+ self::$anyChunksDestroyed[$this->interpreterId] = true;
+ }
+ }
+}