This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Whoops - php errors for cool kids |
||
4 | * @author Filipe Dobreira <http://github.com/filp> |
||
5 | */ |
||
6 | |||
7 | namespace Whoops\Exception; |
||
8 | |||
9 | use Whoops\Util\Misc; |
||
10 | |||
11 | class Inspector |
||
12 | { |
||
13 | /** |
||
14 | * @var \Throwable |
||
15 | */ |
||
16 | private $exception; |
||
17 | |||
18 | /** |
||
19 | * @var \Whoops\Exception\FrameCollection |
||
20 | */ |
||
21 | private $frames; |
||
22 | |||
23 | /** |
||
24 | * @var \Whoops\Exception\Inspector |
||
25 | */ |
||
26 | private $previousExceptionInspector; |
||
27 | |||
28 | /** |
||
29 | * @var \Throwable[] |
||
30 | */ |
||
31 | private $previousExceptions; |
||
32 | |||
33 | /** |
||
34 | * @param \Throwable $exception The exception to inspect |
||
35 | */ |
||
36 | 5 | public function __construct($exception) |
|
37 | { |
||
38 | 5 | $this->exception = $exception; |
|
39 | 5 | } |
|
40 | |||
41 | /** |
||
42 | * @return \Throwable |
||
43 | */ |
||
44 | 3 | public function getException() |
|
45 | { |
||
46 | 3 | return $this->exception; |
|
47 | } |
||
48 | |||
49 | /** |
||
50 | * @return string |
||
51 | */ |
||
52 | 2 | public function getExceptionName() |
|
53 | { |
||
54 | 2 | return get_class($this->exception); |
|
55 | } |
||
56 | |||
57 | /** |
||
58 | * @return string |
||
59 | */ |
||
60 | public function getExceptionMessage() |
||
61 | { |
||
62 | return $this->extractDocrefUrl($this->exception->getMessage())['message']; |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * @return string[] |
||
67 | */ |
||
68 | 1 | public function getPreviousExceptionMessages() |
|
69 | { |
||
70 | return array_map(function ($prev) { |
||
71 | /** @var \Throwable $prev */ |
||
72 | 1 | return $this->extractDocrefUrl($prev->getMessage())['message']; |
|
73 | 1 | }, $this->getPreviousExceptions()); |
|
74 | } |
||
75 | |||
76 | /** |
||
77 | * @return int[] |
||
78 | */ |
||
79 | public function getPreviousExceptionCodes() |
||
80 | { |
||
81 | 1 | return array_map(function ($prev) { |
|
82 | /** @var \Throwable $prev */ |
||
83 | 1 | return $prev->getCode(); |
|
84 | 1 | }, $this->getPreviousExceptions()); |
|
85 | } |
||
86 | |||
87 | /** |
||
88 | * Returns a url to the php-manual related to the underlying error - when available. |
||
89 | * |
||
90 | * @return string|null |
||
91 | */ |
||
92 | public function getExceptionDocrefUrl() |
||
93 | { |
||
94 | return $this->extractDocrefUrl($this->exception->getMessage())['url']; |
||
95 | } |
||
96 | |||
97 | private function extractDocrefUrl($message) |
||
98 | { |
||
99 | $docref = [ |
||
100 | 'message' => $message, |
||
101 | 'url' => null, |
||
102 | ]; |
||
103 | |||
104 | // php embbeds urls to the manual into the Exception message with the following ini-settings defined |
||
105 | // http://php.net/manual/en/errorfunc.configuration.php#ini.docref-root |
||
106 | if (!ini_get('html_errors') || !ini_get('docref_root')) { |
||
107 | return $docref; |
||
108 | } |
||
109 | |||
110 | $pattern = "/\[<a href='([^']+)'>(?:[^<]+)<\/a>\]/"; |
||
111 | if (preg_match($pattern, $message, $matches)) { |
||
112 | // -> strip those automatically generated links from the exception message |
||
113 | $docref['message'] = preg_replace($pattern, '', $message, 1); |
||
114 | $docref['url'] = $matches[1]; |
||
115 | } |
||
116 | |||
117 | return $docref; |
||
118 | } |
||
119 | |||
120 | /** |
||
121 | * Does the wrapped Exception has a previous Exception? |
||
122 | * @return bool |
||
123 | */ |
||
124 | 2 | public function hasPreviousException() |
|
125 | { |
||
126 | 2 | return $this->previousExceptionInspector || $this->exception->getPrevious(); |
|
127 | } |
||
128 | |||
129 | /** |
||
130 | * Returns an Inspector for a previous Exception, if any. |
||
131 | * @todo Clean this up a bit, cache stuff a bit better. |
||
132 | * @return Inspector |
||
133 | */ |
||
134 | 2 | public function getPreviousExceptionInspector() |
|
135 | { |
||
136 | 2 | if ($this->previousExceptionInspector === null) { |
|
137 | 2 | $previousException = $this->exception->getPrevious(); |
|
138 | |||
139 | 2 | if ($previousException) { |
|
140 | 1 | $this->previousExceptionInspector = new Inspector($previousException); |
|
141 | 1 | } |
|
142 | 2 | } |
|
143 | |||
144 | 2 | return $this->previousExceptionInspector; |
|
145 | } |
||
146 | |||
147 | |||
148 | /** |
||
149 | * Returns an array of all previous exceptions for this inspector's exception |
||
150 | * @return \Throwable[] |
||
151 | */ |
||
152 | 2 | public function getPreviousExceptions() |
|
153 | { |
||
154 | 2 | if ($this->previousExceptions === null) { |
|
155 | 2 | $this->previousExceptions = []; |
|
156 | |||
157 | 2 | $prev = $this->exception->getPrevious(); |
|
158 | 2 | while ($prev !== null) { |
|
159 | 1 | $this->previousExceptions[] = $prev; |
|
160 | 1 | $prev = $prev->getPrevious(); |
|
161 | 1 | } |
|
162 | 2 | } |
|
163 | |||
164 | 2 | return $this->previousExceptions; |
|
165 | } |
||
166 | |||
167 | /** |
||
168 | * Returns an iterator for the inspected exception's |
||
169 | * frames. |
||
170 | * @return \Whoops\Exception\FrameCollection |
||
171 | */ |
||
172 | 3 | public function getFrames() |
|
173 | { |
||
174 | 3 | if ($this->frames === null) { |
|
175 | 3 | $frames = $this->getTrace($this->exception); |
|
176 | |||
177 | // Fill empty line/file info for call_user_func_array usages (PHP Bug #44428) |
||
178 | 3 | foreach ($frames as $k => $frame) { |
|
179 | 3 | if (empty($frame['file'])) { |
|
180 | // Default values when file and line are missing |
||
181 | 3 | $file = '[internal]'; |
|
182 | 3 | $line = 0; |
|
183 | |||
184 | 3 | $next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : []; |
|
185 | |||
186 | 3 | if ($this->isValidNextFrame($next_frame)) { |
|
187 | $file = $next_frame['file']; |
||
188 | $line = $next_frame['line']; |
||
189 | } |
||
190 | |||
191 | 3 | $frames[$k]['file'] = $file; |
|
192 | 3 | $frames[$k]['line'] = $line; |
|
193 | 3 | } |
|
194 | 3 | } |
|
195 | |||
196 | // Find latest non-error handling frame index ($i) used to remove error handling frames |
||
197 | 3 | $i = 0; |
|
198 | 3 | foreach ($frames as $k => $frame) { |
|
199 | 3 | if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) { |
|
200 | $i = $k; |
||
201 | } |
||
202 | 3 | } |
|
203 | |||
204 | // Remove error handling frames |
||
205 | 3 | if ($i > 0) { |
|
206 | array_splice($frames, 0, $i); |
||
207 | } |
||
208 | |||
209 | 3 | $firstFrame = $this->getFrameFromException($this->exception); |
|
210 | 3 | array_unshift($frames, $firstFrame); |
|
211 | |||
212 | 3 | $this->frames = new FrameCollection($frames); |
|
213 | |||
214 | 3 | if ($previousInspector = $this->getPreviousExceptionInspector()) { |
|
215 | // Keep outer frame on top of the inner one |
||
216 | 1 | $outerFrames = $this->frames; |
|
217 | 1 | $newFrames = clone $previousInspector->getFrames(); |
|
218 | // I assume it will always be set, but let's be safe |
||
219 | 1 | if (isset($newFrames[0])) { |
|
220 | 1 | $newFrames[0]->addComment( |
|
221 | 1 | $previousInspector->getExceptionMessage(), |
|
222 | 'Exception message:' |
||
223 | 1 | ); |
|
224 | 1 | } |
|
225 | 1 | $newFrames->prependFrames($outerFrames->topDiff($newFrames)); |
|
0 ignored issues
–
show
|
|||
226 | 1 | $this->frames = $newFrames; |
|
227 | 1 | } |
|
228 | 3 | } |
|
229 | |||
230 | 3 | return $this->frames; |
|
231 | } |
||
232 | |||
233 | /** |
||
234 | * Gets the backtrace from an exception. |
||
235 | * |
||
236 | * If xdebug is installed |
||
237 | * |
||
238 | * @param \Throwable $e |
||
239 | * @return array |
||
240 | */ |
||
241 | 1 | protected function getTrace($e) |
|
242 | { |
||
243 | 1 | $traces = $e->getTrace(); |
|
244 | |||
245 | // Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default |
||
246 | 1 | if (!$e instanceof \ErrorException) { |
|
247 | 1 | return $traces; |
|
248 | } |
||
249 | |||
250 | if (!Misc::isLevelFatal($e->getSeverity())) { |
||
251 | return $traces; |
||
252 | } |
||
253 | |||
254 | if (!extension_loaded('xdebug') || !function_exists('xdebug_is_enabled') || !xdebug_is_enabled()) { |
||
255 | return $traces; |
||
256 | } |
||
257 | |||
258 | // Use xdebug to get the full stack trace and remove the shutdown handler stack trace |
||
259 | $stack = array_reverse(xdebug_get_function_stack()); |
||
260 | $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); |
||
261 | $traces = array_diff_key($stack, $trace); |
||
262 | |||
263 | return $traces; |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * Given an exception, generates an array in the format |
||
268 | * generated by Exception::getTrace() |
||
269 | * @param \Throwable $exception |
||
270 | * @return array |
||
271 | */ |
||
272 | 1 | protected function getFrameFromException($exception) |
|
273 | { |
||
274 | return [ |
||
275 | 1 | 'file' => $exception->getFile(), |
|
276 | 1 | 'line' => $exception->getLine(), |
|
277 | 1 | 'class' => get_class($exception), |
|
278 | 'args' => [ |
||
279 | 1 | $exception->getMessage(), |
|
280 | 1 | ], |
|
281 | 1 | ]; |
|
282 | } |
||
283 | |||
284 | /** |
||
285 | * Given an error, generates an array in the format |
||
286 | * generated by ErrorException |
||
287 | * @param ErrorException $exception |
||
288 | * @return array |
||
289 | */ |
||
290 | protected function getFrameFromError(ErrorException $exception) |
||
291 | { |
||
292 | return [ |
||
293 | 'file' => $exception->getFile(), |
||
294 | 'line' => $exception->getLine(), |
||
295 | 'class' => null, |
||
296 | 'args' => [], |
||
297 | ]; |
||
298 | } |
||
299 | |||
300 | /** |
||
301 | * Determine if the frame can be used to fill in previous frame's missing info |
||
302 | * happens for call_user_func and call_user_func_array usages (PHP Bug #44428) |
||
303 | * |
||
304 | * @param array $frame |
||
305 | * @return bool |
||
306 | */ |
||
307 | 1 | protected function isValidNextFrame(array $frame) |
|
308 | { |
||
309 | 1 | if (empty($frame['file'])) { |
|
310 | return false; |
||
311 | } |
||
312 | |||
313 | 1 | if (empty($frame['line'])) { |
|
314 | return false; |
||
315 | } |
||
316 | |||
317 | 1 | if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) { |
|
318 | 1 | return false; |
|
319 | } |
||
320 | |||
321 | return true; |
||
322 | } |
||
323 | } |
||
324 |
It seems like the type of the argument is not accepted by the function/method which you are calling.
In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.
We suggest to add an explicit type cast like in the following example: