Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 68 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
LuaSandboxInterpreter | |
0.00% |
0 / 68 |
|
0.00% |
0 / 13 |
812 | |
0.00% |
0 / 1 |
checkLuaSandboxVersion | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
convertSandboxError | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
30 | |||
loadString | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
registerLibrary | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
callFunction | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
wrapPhpFunction | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isLuaFunction | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPeakMemoryUsage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCPUUsage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getProfilerFunctionReport | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
pauseUsageTimer | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
unpauseUsageTimer | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Scribunto\Engines\LuaSandbox; |
4 | |
5 | use LuaSandbox; |
6 | use LuaSandboxError; |
7 | use LuaSandboxFunction; |
8 | use LuaSandboxTimeoutError; |
9 | use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaEngine; |
10 | use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaError; |
11 | use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaInterpreter; |
12 | use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaInterpreterBadVersionError; |
13 | use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaInterpreterNotFoundError; |
14 | use RuntimeException; |
15 | use UtfNormal\Validator; |
16 | |
17 | class 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 | } |