Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
LuaSandboxInterpreter
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 13
812
0.00% covered (danger)
0.00%
0 / 1
 checkLuaSandboxVersion
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 __construct
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 convertSandboxError
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 loadString
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 registerLibrary
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 callFunction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 wrapPhpFunction
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isLuaFunction
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPeakMemoryUsage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCPUUsage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getProfilerFunctionReport
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 pauseUsageTimer
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 unpauseUsageTimer
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Scribunto\Engines\LuaSandbox;
4
5use LuaSandbox;
6use LuaSandboxError;
7use LuaSandboxFunction;
8use LuaSandboxTimeoutError;
9use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaEngine;
10use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaError;
11use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaInterpreter;
12use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaInterpreterBadVersionError;
13use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaInterpreterNotFoundError;
14use RuntimeException;
15use UtfNormal\Validator;
16
17class LuaSandboxInterpreter extends LuaInterpreter {
18    /**
19     * @var LuaEngine
20     */
21    public $engine;
22
23    /**
24     * @var LuaSandbox
25     */
26    public $sandbox;
27
28    /**
29     * @var bool
30     */
31    public $profilerEnabled;
32
33    public const SAMPLES = 0;
34    public const SECONDS = 1;
35    public const PERCENT = 2;
36
37    /**
38     * Check that php-luasandbox is available and of a recent-enough version
39     * @throws LuaInterpreterNotFoundError
40     * @throws LuaInterpreterBadVersionError
41     */
42    public static function checkLuaSandboxVersion() {
43        if ( !extension_loaded( 'luasandbox' ) ) {
44            throw new LuaInterpreterNotFoundError(
45                'The luasandbox extension is not present, this engine cannot be used.' );
46        }
47
48        if ( !is_callable( [ LuaSandbox::class, 'getVersionInfo' ] ) ) {
49            throw new LuaInterpreterBadVersionError(
50                'The luasandbox extension is too old (version 1.6+ is required), ' .
51                    'this engine cannot be used.'
52            );
53        }
54    }
55
56    /**
57     * @param LuaEngine $engine
58     * @param array $options
59     */
60    public function __construct( $engine, array $options ) {
61        self::checkLuaSandboxVersion();
62
63        $this->engine = $engine;
64        $this->sandbox = new LuaSandbox;
65        $this->sandbox->setMemoryLimit( $options['memoryLimit'] );
66        $this->sandbox->setCPULimit( $options['cpuLimit'] );
67        if ( !isset( $options['profilerPeriod'] ) ) {
68            $options['profilerPeriod'] = 0.02;
69        }
70        if ( $options['profilerPeriod'] ) {
71            $this->profilerEnabled = true;
72            $this->sandbox->enableProfiler( $options['profilerPeriod'] );
73        }
74    }
75
76    /**
77     * Convert a LuaSandboxError to a LuaError
78     * @param LuaSandboxError $e
79     * @return LuaError
80     */
81    protected function convertSandboxError( LuaSandboxError $e ) {
82        $opts = [];
83        if ( isset( $e->luaTrace ) ) {
84            $trace = $e->luaTrace;
85            foreach ( $trace as &$val ) {
86                $val = array_map( static function ( $val ) {
87                    if ( is_string( $val ) ) {
88                        $val = Validator::cleanUp( $val );
89                    }
90                    return $val;
91                }, $val );
92            }
93            $opts['trace'] = $trace;
94        }
95        $message = Validator::cleanUp( $e->getMessage() );
96        if ( preg_match( '/^(.*?):(\d+): (.*)$/', $message, $m ) ) {
97            $opts['module'] = $m[1];
98            $opts['line'] = $m[2];
99            $message = $m[3];
100        }
101        return $this->engine->newLuaError( $message, $opts );
102    }
103
104    /**
105     * @param string $text
106     * @param string $chunkName
107     * @return mixed
108     * @throws LuaError
109     */
110    public function loadString( $text, $chunkName ) {
111        try {
112            return $this->sandbox->loadString( $text, $chunkName );
113        } catch ( LuaSandboxError $e ) {
114            throw $this->convertSandboxError( $e );
115        }
116    }
117
118    /** @inheritDoc */
119    public function registerLibrary( $name, array $functions ) {
120        $realLibrary = [];
121        foreach ( $functions as $funcName => $callback ) {
122            $realLibrary[$funcName] = [
123                new LuaSandboxCallback( $callback ),
124                $funcName ];
125        }
126        $this->sandbox->registerLibrary( $name, $realLibrary );
127
128        # TODO: replace this with
129        # $this->sandbox->registerVirtualLibrary(
130        #     $name, [ $this, 'callback' ], $functions );
131    }
132
133    /** @inheritDoc */
134    public function callFunction( $func, ...$args ) {
135        try {
136            $ret = $func->call( ...$args );
137            if ( $ret === false ) {
138                // Per the documentation on LuaSandboxFunction::call, a return value
139                // of false means that something went wrong and it's PHP's fault,
140                // so throw a "real" exception.
141                throw new RuntimeException(
142                    __METHOD__ . ': LuaSandboxFunction::call returned false' );
143            }
144            return $ret;
145        } catch ( LuaSandboxTimeoutError $e ) {
146            throw $this->engine->newException( 'scribunto-common-timeout' );
147        } catch ( LuaSandboxError $e ) {
148            throw $this->convertSandboxError( $e );
149        }
150    }
151
152    /** @inheritDoc */
153    public function wrapPhpFunction( $callable ) {
154        return $this->sandbox->wrapPhpFunction( $callable );
155    }
156
157    /** @inheritDoc */
158    public function isLuaFunction( $object ) {
159        return $object instanceof LuaSandboxFunction;
160    }
161
162    /**
163     * @return int
164     */
165    public function getPeakMemoryUsage() {
166        return $this->sandbox->getPeakMemoryUsage();
167    }
168
169    /**
170     * @return float
171     */
172    public function getCPUUsage() {
173        return $this->sandbox->getCPUUsage();
174    }
175
176    /**
177     * @param int $units self::SAMPLES, self::SECONDS, or self::PERCENT
178     * @return array
179     */
180    public function getProfilerFunctionReport( $units ) {
181        if ( $this->profilerEnabled ) {
182            static $unitsMap;
183            if ( !$unitsMap ) {
184                $unitsMap = [
185                    self::SAMPLES => LuaSandbox::SAMPLES,
186                    self::SECONDS => LuaSandbox::SECONDS,
187                    self::PERCENT => LuaSandbox::PERCENT,
188                ];
189            }
190            return $this->sandbox->getProfilerFunctionReport( $unitsMap[$units] );
191        } else {
192            return [];
193        }
194    }
195
196    public function pauseUsageTimer() {
197        $this->sandbox->pauseUsageTimer();
198    }
199
200    public function unpauseUsageTimer() {
201        $this->sandbox->unpauseUsageTimer();
202    }
203}