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 | * New version of the difference engine |
||
4 | * |
||
5 | * Copyright © 2008 Guy Van den Broeck <[email protected]> |
||
6 | * |
||
7 | * This program is free software; you can redistribute it and/or modify |
||
8 | * it under the terms of the GNU General Public License as published by |
||
9 | * the Free Software Foundation; either version 2 of the License, or |
||
10 | * (at your option) any later version. |
||
11 | * |
||
12 | * This program is distributed in the hope that it will be useful, |
||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
15 | * GNU General Public License for more details. |
||
16 | * |
||
17 | * You should have received a copy of the GNU General Public License |
||
18 | * along with this program; if not, write to the Free Software |
||
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
20 | * http://www.gnu.org/copyleft/gpl.html |
||
21 | * |
||
22 | * @file |
||
23 | * @ingroup DifferenceEngine |
||
24 | */ |
||
25 | use MediaWiki\Diff\ComplexityException; |
||
26 | |||
27 | /** |
||
28 | * This diff implementation is mainly lifted from the LCS algorithm of the Eclipse project which |
||
29 | * in turn is based on Myers' "An O(ND) difference algorithm and its variations" |
||
30 | * (http://citeseer.ist.psu.edu/myers86ond.html) with range compression (see Wu et al.'s |
||
31 | * "An O(NP) Sequence Comparison Algorithm"). |
||
32 | * |
||
33 | * This implementation supports an upper bound on the execution time. |
||
34 | * |
||
35 | * Some ideas (and a bit of code) are from analyze.c, from GNU |
||
36 | * diffutils-2.7, which can be found at: |
||
37 | * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz |
||
38 | * |
||
39 | * Complexity: O((M + N)D) worst case time, O(M + N + D^2) expected time, O(M + N) space |
||
40 | * |
||
41 | * @author Guy Van den Broeck, Geoffrey T. Dairiki, Tim Starling |
||
42 | * @ingroup DifferenceEngine |
||
43 | */ |
||
44 | class DiffEngine { |
||
45 | |||
46 | // Input variables |
||
47 | private $from; |
||
48 | private $to; |
||
49 | private $m; |
||
50 | private $n; |
||
51 | |||
52 | private $tooLong; |
||
53 | private $powLimit; |
||
54 | |||
55 | protected $bailoutComplexity = 0; |
||
56 | |||
57 | // State variables |
||
58 | private $maxDifferences; |
||
59 | private $lcsLengthCorrectedForHeuristic = false; |
||
60 | |||
61 | // Output variables |
||
62 | public $length; |
||
63 | public $removed; |
||
64 | public $added; |
||
65 | public $heuristicUsed; |
||
66 | |||
67 | function __construct( $tooLong = 2000000, $powLimit = 1.45 ) { |
||
68 | $this->tooLong = $tooLong; |
||
69 | $this->powLimit = $powLimit; |
||
70 | } |
||
71 | |||
72 | /** |
||
73 | * Performs diff |
||
74 | * |
||
75 | * @param string[] $from_lines |
||
76 | * @param string[] $to_lines |
||
77 | * @throws ComplexityException |
||
78 | * |
||
79 | * @return DiffOp[] |
||
80 | */ |
||
81 | public function diff( $from_lines, $to_lines ) { |
||
82 | |||
83 | // Diff and store locally |
||
84 | $this->diffInternal( $from_lines, $to_lines ); |
||
85 | |||
86 | // Merge edits when possible |
||
87 | $this->shiftBoundaries( $from_lines, $this->removed, $this->added ); |
||
88 | $this->shiftBoundaries( $to_lines, $this->added, $this->removed ); |
||
89 | |||
90 | // Compute the edit operations. |
||
91 | $n_from = count( $from_lines ); |
||
92 | $n_to = count( $to_lines ); |
||
93 | |||
94 | $edits = []; |
||
95 | $xi = $yi = 0; |
||
96 | while ( $xi < $n_from || $yi < $n_to ) { |
||
97 | assert( $yi < $n_to || $this->removed[$xi] ); |
||
98 | assert( $xi < $n_from || $this->added[$yi] ); |
||
99 | |||
100 | // Skip matching "snake". |
||
101 | $copy = []; |
||
102 | while ( $xi < $n_from && $yi < $n_to |
||
103 | && !$this->removed[$xi] && !$this->added[$yi] |
||
104 | ) { |
||
105 | $copy[] = $from_lines[$xi++]; |
||
106 | ++$yi; |
||
107 | } |
||
108 | if ( $copy ) { |
||
109 | $edits[] = new DiffOpCopy( $copy ); |
||
110 | } |
||
111 | |||
112 | // Find deletes & adds. |
||
113 | $delete = []; |
||
114 | while ( $xi < $n_from && $this->removed[$xi] ) { |
||
115 | $delete[] = $from_lines[$xi++]; |
||
116 | } |
||
117 | |||
118 | $add = []; |
||
119 | while ( $yi < $n_to && $this->added[$yi] ) { |
||
120 | $add[] = $to_lines[$yi++]; |
||
121 | } |
||
122 | |||
123 | if ( $delete && $add ) { |
||
124 | $edits[] = new DiffOpChange( $delete, $add ); |
||
125 | } elseif ( $delete ) { |
||
126 | $edits[] = new DiffOpDelete( $delete ); |
||
127 | } elseif ( $add ) { |
||
128 | $edits[] = new DiffOpAdd( $add ); |
||
129 | } |
||
130 | } |
||
131 | |||
132 | return $edits; |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * Sets the complexity (in comparison operations) that can't be exceeded |
||
137 | * @param int $value |
||
138 | */ |
||
139 | public function setBailoutComplexity( $value ) { |
||
140 | $this->bailoutComplexity = $value; |
||
141 | } |
||
142 | |||
143 | /** |
||
144 | * Adjust inserts/deletes of identical lines to join changes |
||
145 | * as much as possible. |
||
146 | * |
||
147 | * We do something when a run of changed lines include a |
||
148 | * line at one end and has an excluded, identical line at the other. |
||
149 | * We are free to choose which identical line is included. |
||
150 | * `compareseq' usually chooses the one at the beginning, |
||
151 | * but usually it is cleaner to consider the following identical line |
||
152 | * to be the "change". |
||
153 | * |
||
154 | * This is extracted verbatim from analyze.c (GNU diffutils-2.7). |
||
155 | * |
||
156 | * @param string[] $lines |
||
157 | * @param string[] $changed |
||
158 | * @param string[] $other_changed |
||
159 | */ |
||
160 | private function shiftBoundaries( array $lines, array &$changed, array $other_changed ) { |
||
161 | $i = 0; |
||
162 | $j = 0; |
||
163 | |||
164 | assert( count( $lines ) == count( $changed ) ); |
||
165 | $len = count( $lines ); |
||
166 | $other_len = count( $other_changed ); |
||
167 | |||
168 | while ( 1 ) { |
||
169 | /* |
||
170 | * Scan forwards to find beginning of another run of changes. |
||
171 | * Also keep track of the corresponding point in the other file. |
||
172 | * |
||
173 | * Throughout this code, $i and $j are adjusted together so that |
||
174 | * the first $i elements of $changed and the first $j elements |
||
175 | * of $other_changed both contain the same number of zeros |
||
176 | * (unchanged lines). |
||
177 | * Furthermore, $j is always kept so that $j == $other_len or |
||
178 | * $other_changed[$j] == false. |
||
179 | */ |
||
180 | while ( $j < $other_len && $other_changed[$j] ) { |
||
181 | $j++; |
||
182 | } |
||
183 | |||
184 | while ( $i < $len && !$changed[$i] ) { |
||
185 | assert( $j < $other_len && ! $other_changed[$j] ); |
||
186 | $i++; |
||
187 | $j++; |
||
188 | while ( $j < $other_len && $other_changed[$j] ) { |
||
189 | $j++; |
||
190 | } |
||
191 | } |
||
192 | |||
193 | if ( $i == $len ) { |
||
194 | break; |
||
195 | } |
||
196 | |||
197 | $start = $i; |
||
198 | |||
199 | // Find the end of this run of changes. |
||
200 | while ( ++$i < $len && $changed[$i] ) { |
||
201 | continue; |
||
202 | } |
||
203 | |||
204 | do { |
||
205 | /* |
||
206 | * Record the length of this run of changes, so that |
||
207 | * we can later determine whether the run has grown. |
||
208 | */ |
||
209 | $runlength = $i - $start; |
||
210 | |||
211 | /* |
||
212 | * Move the changed region back, so long as the |
||
213 | * previous unchanged line matches the last changed one. |
||
214 | * This merges with previous changed regions. |
||
215 | */ |
||
216 | while ( $start > 0 && $lines[$start - 1] == $lines[$i - 1] ) { |
||
217 | $changed[--$start] = 1; |
||
218 | $changed[--$i] = false; |
||
219 | while ( $start > 0 && $changed[$start - 1] ) { |
||
220 | $start--; |
||
221 | } |
||
222 | assert( $j > 0 ); |
||
223 | while ( $other_changed[--$j] ) { |
||
224 | continue; |
||
225 | } |
||
226 | assert( $j >= 0 && !$other_changed[$j] ); |
||
227 | } |
||
228 | |||
229 | /* |
||
230 | * Set CORRESPONDING to the end of the changed run, at the last |
||
231 | * point where it corresponds to a changed run in the other file. |
||
232 | * CORRESPONDING == LEN means no such point has been found. |
||
233 | */ |
||
234 | $corresponding = $j < $other_len ? $i : $len; |
||
235 | |||
236 | /* |
||
237 | * Move the changed region forward, so long as the |
||
238 | * first changed line matches the following unchanged one. |
||
239 | * This merges with following changed regions. |
||
240 | * Do this second, so that if there are no merges, |
||
241 | * the changed region is moved forward as far as possible. |
||
242 | */ |
||
243 | while ( $i < $len && $lines[$start] == $lines[$i] ) { |
||
244 | $changed[$start++] = false; |
||
245 | $changed[$i++] = 1; |
||
246 | while ( $i < $len && $changed[$i] ) { |
||
247 | $i++; |
||
248 | } |
||
249 | |||
250 | assert( $j < $other_len && ! $other_changed[$j] ); |
||
251 | $j++; |
||
252 | if ( $j < $other_len && $other_changed[$j] ) { |
||
253 | $corresponding = $i; |
||
254 | while ( $j < $other_len && $other_changed[$j] ) { |
||
255 | $j++; |
||
256 | } |
||
257 | } |
||
258 | } |
||
259 | } while ( $runlength != $i - $start ); |
||
260 | |||
261 | /* |
||
262 | * If possible, move the fully-merged run of changes |
||
263 | * back to a corresponding run in the other file. |
||
264 | */ |
||
265 | while ( $corresponding < $i ) { |
||
266 | $changed[--$start] = 1; |
||
267 | $changed[--$i] = 0; |
||
268 | assert( $j > 0 ); |
||
269 | while ( $other_changed[--$j] ) { |
||
270 | continue; |
||
271 | } |
||
272 | assert( $j >= 0 && !$other_changed[$j] ); |
||
273 | } |
||
274 | } |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * @param string[] $from |
||
279 | * @param string[] $to |
||
280 | * @throws ComplexityException |
||
281 | */ |
||
282 | protected function diffInternal( array $from, array $to ) { |
||
283 | // remember initial lengths |
||
284 | $m = count( $from ); |
||
285 | $n = count( $to ); |
||
286 | |||
287 | $this->heuristicUsed = false; |
||
288 | |||
289 | // output |
||
290 | $removed = $m > 0 ? array_fill( 0, $m, true ) : []; |
||
291 | $added = $n > 0 ? array_fill( 0, $n, true ) : []; |
||
292 | |||
293 | // reduce the complexity for the next step (intentionally done twice) |
||
294 | // remove common tokens at the start |
||
295 | $i = 0; |
||
296 | while ( $i < $m && $i < $n && $from[$i] === $to[$i] ) { |
||
297 | $removed[$i] = $added[$i] = false; |
||
298 | unset( $from[$i], $to[$i] ); |
||
299 | ++$i; |
||
300 | } |
||
301 | |||
302 | // remove common tokens at the end |
||
303 | $j = 1; |
||
304 | while ( $i + $j <= $m && $i + $j <= $n && $from[$m - $j] === $to[$n - $j] ) { |
||
305 | $removed[$m - $j] = $added[$n - $j] = false; |
||
306 | unset( $from[$m - $j], $to[$n - $j] ); |
||
307 | ++$j; |
||
308 | } |
||
309 | |||
310 | $this->from = $newFromIndex = $this->to = $newToIndex = []; |
||
311 | |||
312 | // remove tokens not in both sequences |
||
313 | $shared = []; |
||
314 | foreach ( $from as $key ) { |
||
315 | $shared[$key] = false; |
||
316 | } |
||
317 | |||
318 | foreach ( $to as $index => &$el ) { |
||
319 | if ( array_key_exists( $el, $shared ) ) { |
||
320 | // keep it |
||
321 | $this->to[] = $el; |
||
322 | $shared[$el] = true; |
||
323 | $newToIndex[] = $index; |
||
324 | } |
||
325 | } |
||
326 | foreach ( $from as $index => &$el ) { |
||
327 | if ( $shared[$el] ) { |
||
328 | // keep it |
||
329 | $this->from[] = $el; |
||
330 | $newFromIndex[] = $index; |
||
331 | } |
||
332 | } |
||
333 | |||
334 | unset( $shared, $from, $to ); |
||
335 | |||
336 | $this->m = count( $this->from ); |
||
337 | $this->n = count( $this->to ); |
||
338 | |||
339 | if ( $this->bailoutComplexity > 0 && $this->m * $this->n > $this->bailoutComplexity ) { |
||
340 | throw new ComplexityException(); |
||
341 | } |
||
342 | |||
343 | $this->removed = $this->m > 0 ? array_fill( 0, $this->m, true ) : []; |
||
344 | $this->added = $this->n > 0 ? array_fill( 0, $this->n, true ) : []; |
||
345 | |||
346 | if ( $this->m == 0 || $this->n == 0 ) { |
||
347 | $this->length = 0; |
||
348 | } else { |
||
349 | $this->maxDifferences = ceil( ( $this->m + $this->n ) / 2.0 ); |
||
350 | if ( $this->m * $this->n > $this->tooLong ) { |
||
351 | // limit complexity to D^POW_LIMIT for long sequences |
||
352 | $this->maxDifferences = floor( pow( $this->maxDifferences, $this->powLimit - 1.0 ) ); |
||
353 | wfDebug( "Limiting max number of differences to $this->maxDifferences\n" ); |
||
354 | } |
||
355 | |||
356 | /* |
||
357 | * The common prefixes and suffixes are always part of some LCS, include |
||
358 | * them now to reduce our search space |
||
359 | */ |
||
360 | $max = min( $this->m, $this->n ); |
||
361 | for ( $forwardBound = 0; $forwardBound < $max |
||
362 | && $this->from[$forwardBound] === $this->to[$forwardBound]; |
||
363 | ++$forwardBound |
||
364 | ) { |
||
365 | $this->removed[$forwardBound] = $this->added[$forwardBound] = false; |
||
366 | } |
||
367 | |||
368 | $backBoundL1 = $this->m - 1; |
||
369 | $backBoundL2 = $this->n - 1; |
||
370 | |||
371 | while ( $backBoundL1 >= $forwardBound && $backBoundL2 >= $forwardBound |
||
372 | && $this->from[$backBoundL1] === $this->to[$backBoundL2] |
||
373 | ) { |
||
374 | $this->removed[$backBoundL1--] = $this->added[$backBoundL2--] = false; |
||
375 | } |
||
376 | |||
377 | $temp = array_fill( 0, $this->m + $this->n + 1, 0 ); |
||
378 | $V = [ $temp, $temp ]; |
||
379 | $snake = [ 0, 0, 0 ]; |
||
380 | |||
381 | $this->length = $forwardBound + $this->m - $backBoundL1 - 1 |
||
382 | + $this->lcs_rec( |
||
383 | $forwardBound, |
||
384 | $backBoundL1, |
||
385 | $forwardBound, |
||
386 | $backBoundL2, |
||
387 | $V, |
||
388 | $snake |
||
389 | ); |
||
390 | } |
||
391 | |||
392 | $this->m = $m; |
||
393 | $this->n = $n; |
||
394 | |||
395 | $this->length += $i + $j - 1; |
||
396 | |||
397 | foreach ( $this->removed as $key => &$removed_elem ) { |
||
398 | if ( !$removed_elem ) { |
||
399 | $removed[$newFromIndex[$key]] = false; |
||
400 | } |
||
401 | } |
||
402 | foreach ( $this->added as $key => &$added_elem ) { |
||
403 | if ( !$added_elem ) { |
||
404 | $added[$newToIndex[$key]] = false; |
||
405 | } |
||
406 | } |
||
407 | $this->removed = $removed; |
||
408 | $this->added = $added; |
||
409 | } |
||
410 | |||
411 | function diff_range( $from_lines, $to_lines ) { |
||
412 | // Diff and store locally |
||
413 | $this->diff( $from_lines, $to_lines ); |
||
414 | unset( $from_lines, $to_lines ); |
||
415 | |||
416 | $ranges = []; |
||
417 | $xi = $yi = 0; |
||
418 | while ( $xi < $this->m || $yi < $this->n ) { |
||
419 | // Matching "snake". |
||
420 | while ( $xi < $this->m && $yi < $this->n |
||
421 | && !$this->removed[$xi] |
||
422 | && !$this->added[$yi] |
||
423 | ) { |
||
424 | ++$xi; |
||
425 | ++$yi; |
||
426 | } |
||
427 | // Find deletes & adds. |
||
428 | $xstart = $xi; |
||
429 | while ( $xi < $this->m && $this->removed[$xi] ) { |
||
430 | ++$xi; |
||
431 | } |
||
432 | |||
433 | $ystart = $yi; |
||
434 | while ( $yi < $this->n && $this->added[$yi] ) { |
||
435 | ++$yi; |
||
436 | } |
||
437 | |||
438 | if ( $xi > $xstart || $yi > $ystart ) { |
||
439 | $ranges[] = new RangeDifference( $xstart, $xi, $ystart, $yi ); |
||
440 | } |
||
441 | } |
||
442 | |||
443 | return $ranges; |
||
444 | } |
||
445 | |||
446 | private function lcs_rec( $bottoml1, $topl1, $bottoml2, $topl2, &$V, &$snake ) { |
||
447 | // check that both sequences are non-empty |
||
448 | if ( $bottoml1 > $topl1 || $bottoml2 > $topl2 ) { |
||
449 | return 0; |
||
450 | } |
||
451 | |||
452 | $d = $this->find_middle_snake( $bottoml1, $topl1, $bottoml2, |
||
453 | $topl2, $V, $snake ); |
||
454 | |||
455 | // need to store these so we don't lose them when they're |
||
456 | // overwritten by the recursion |
||
457 | $len = $snake[2]; |
||
458 | $startx = $snake[0]; |
||
459 | $starty = $snake[1]; |
||
460 | |||
461 | // the middle snake is part of the LCS, store it |
||
462 | View Code Duplication | for ( $i = 0; $i < $len; ++$i ) { |
|
463 | $this->removed[$startx + $i] = $this->added[$starty + $i] = false; |
||
464 | } |
||
465 | |||
466 | if ( $d > 1 ) { |
||
467 | return $len |
||
468 | + $this->lcs_rec( $bottoml1, $startx - 1, $bottoml2, |
||
469 | $starty - 1, $V, $snake ) |
||
470 | + $this->lcs_rec( $startx + $len, $topl1, $starty + $len, |
||
471 | $topl2, $V, $snake ); |
||
472 | } elseif ( $d == 1 ) { |
||
473 | /* |
||
474 | * In this case the sequences differ by exactly 1 line. We have |
||
475 | * already saved all the lines after the difference in the for loop |
||
476 | * above, now we need to save all the lines before the difference. |
||
477 | */ |
||
478 | $max = min( $startx - $bottoml1, $starty - $bottoml2 ); |
||
479 | View Code Duplication | for ( $i = 0; $i < $max; ++$i ) { |
|
480 | $this->removed[$bottoml1 + $i] = |
||
481 | $this->added[$bottoml2 + $i] = false; |
||
482 | } |
||
483 | |||
484 | return $max + $len; |
||
485 | } |
||
486 | |||
487 | return $len; |
||
488 | } |
||
489 | |||
490 | private function find_middle_snake( $bottoml1, $topl1, $bottoml2, $topl2, &$V, &$snake ) { |
||
491 | $from = &$this->from; |
||
492 | $to = &$this->to; |
||
493 | $V0 = &$V[0]; |
||
494 | $V1 = &$V[1]; |
||
495 | $snake0 = &$snake[0]; |
||
496 | $snake1 = &$snake[1]; |
||
497 | $snake2 = &$snake[2]; |
||
498 | $bottoml1_min_1 = $bottoml1 - 1; |
||
499 | $bottoml2_min_1 = $bottoml2 - 1; |
||
500 | $N = $topl1 - $bottoml1_min_1; |
||
501 | $M = $topl2 - $bottoml2_min_1; |
||
502 | $delta = $N - $M; |
||
503 | $maxabsx = $N + $bottoml1; |
||
504 | $maxabsy = $M + $bottoml2; |
||
505 | $limit = min( $this->maxDifferences, ceil( ( $N + $M ) / 2 ) ); |
||
506 | |||
507 | // value_to_add_forward: a 0 or 1 that we add to the start |
||
508 | // offset to make it odd/even |
||
509 | if ( ( $M & 1 ) == 1 ) { |
||
510 | $value_to_add_forward = 1; |
||
511 | } else { |
||
512 | $value_to_add_forward = 0; |
||
513 | } |
||
514 | |||
515 | if ( ( $N & 1 ) == 1 ) { |
||
516 | $value_to_add_backward = 1; |
||
517 | } else { |
||
518 | $value_to_add_backward = 0; |
||
519 | } |
||
520 | |||
521 | $start_forward = -$M; |
||
522 | $end_forward = $N; |
||
523 | $start_backward = -$N; |
||
524 | $end_backward = $M; |
||
525 | |||
526 | $limit_min_1 = $limit - 1; |
||
527 | $limit_plus_1 = $limit + 1; |
||
528 | |||
529 | $V0[$limit_plus_1] = 0; |
||
530 | $V1[$limit_min_1] = $N; |
||
531 | $limit = min( $this->maxDifferences, ceil( ( $N + $M ) / 2 ) ); |
||
532 | |||
533 | if ( ( $delta & 1 ) == 1 ) { |
||
534 | for ( $d = 0; $d <= $limit; ++$d ) { |
||
535 | $start_diag = max( $value_to_add_forward + $start_forward, -$d ); |
||
536 | $end_diag = min( $end_forward, $d ); |
||
537 | $value_to_add_forward = 1 - $value_to_add_forward; |
||
538 | |||
539 | // compute forward furthest reaching paths |
||
540 | for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) { |
||
541 | View Code Duplication | if ( $k == -$d || ( $k < $d |
|
542 | && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] ) |
||
543 | ) { |
||
544 | $x = $V0[$limit_plus_1 + $k]; |
||
545 | } else { |
||
546 | $x = $V0[$limit_min_1 + $k] + 1; |
||
547 | } |
||
548 | |||
549 | $absx = $snake0 = $x + $bottoml1; |
||
550 | $absy = $snake1 = $x - $k + $bottoml2; |
||
551 | |||
552 | View Code Duplication | while ( $absx < $maxabsx && $absy < $maxabsy && $from[$absx] === $to[$absy] ) { |
|
553 | ++$absx; |
||
554 | ++$absy; |
||
555 | } |
||
556 | $x = $absx - $bottoml1; |
||
557 | |||
558 | $snake2 = $absx - $snake0; |
||
559 | $V0[$limit + $k] = $x; |
||
560 | if ( $k >= $delta - $d + 1 && $k <= $delta + $d - 1 |
||
561 | && $x >= $V1[$limit + $k - $delta] |
||
562 | ) { |
||
563 | return 2 * $d - 1; |
||
564 | } |
||
565 | |||
566 | // check to see if we can cut down the diagonal range |
||
567 | View Code Duplication | if ( $x >= $N && $end_forward > $k - 1 ) { |
|
568 | $end_forward = $k - 1; |
||
569 | } elseif ( $absy - $bottoml2 >= $M ) { |
||
570 | $start_forward = $k + 1; |
||
571 | $value_to_add_forward = 0; |
||
572 | } |
||
573 | } |
||
574 | |||
575 | $start_diag = max( $value_to_add_backward + $start_backward, -$d ); |
||
576 | $end_diag = min( $end_backward, $d ); |
||
577 | $value_to_add_backward = 1 - $value_to_add_backward; |
||
578 | |||
579 | // compute backward furthest reaching paths |
||
580 | for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) { |
||
581 | View Code Duplication | if ( $k == $d |
|
582 | || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] ) |
||
583 | ) { |
||
584 | $x = $V1[$limit_min_1 + $k]; |
||
585 | } else { |
||
586 | $x = $V1[$limit_plus_1 + $k] - 1; |
||
587 | } |
||
588 | |||
589 | $y = $x - $k - $delta; |
||
590 | |||
591 | $snake2 = 0; |
||
592 | View Code Duplication | while ( $x > 0 && $y > 0 |
|
593 | && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1] |
||
594 | ) { |
||
595 | --$x; |
||
596 | --$y; |
||
597 | ++$snake2; |
||
598 | } |
||
599 | $V1[$limit + $k] = $x; |
||
600 | |||
601 | // check to see if we can cut down our diagonal range |
||
602 | View Code Duplication | if ( $x <= 0 ) { |
|
603 | $start_backward = $k + 1; |
||
604 | $value_to_add_backward = 0; |
||
605 | } elseif ( $y <= 0 && $end_backward > $k - 1 ) { |
||
606 | $end_backward = $k - 1; |
||
607 | } |
||
608 | } |
||
609 | } |
||
610 | } else { |
||
611 | for ( $d = 0; $d <= $limit; ++$d ) { |
||
612 | $start_diag = max( $value_to_add_forward + $start_forward, -$d ); |
||
613 | $end_diag = min( $end_forward, $d ); |
||
614 | $value_to_add_forward = 1 - $value_to_add_forward; |
||
615 | |||
616 | // compute forward furthest reaching paths |
||
617 | for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) { |
||
618 | View Code Duplication | if ( $k == -$d |
|
619 | || ( $k < $d && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] ) |
||
620 | ) { |
||
621 | $x = $V0[$limit_plus_1 + $k]; |
||
622 | } else { |
||
623 | $x = $V0[$limit_min_1 + $k] + 1; |
||
624 | } |
||
625 | |||
626 | $absx = $snake0 = $x + $bottoml1; |
||
627 | $absy = $snake1 = $x - $k + $bottoml2; |
||
628 | |||
629 | View Code Duplication | while ( $absx < $maxabsx && $absy < $maxabsy && $from[$absx] === $to[$absy] ) { |
|
630 | ++$absx; |
||
631 | ++$absy; |
||
632 | } |
||
633 | $x = $absx - $bottoml1; |
||
634 | $snake2 = $absx - $snake0; |
||
635 | $V0[$limit + $k] = $x; |
||
636 | |||
637 | // check to see if we can cut down the diagonal range |
||
638 | View Code Duplication | if ( $x >= $N && $end_forward > $k - 1 ) { |
|
639 | $end_forward = $k - 1; |
||
640 | } elseif ( $absy - $bottoml2 >= $M ) { |
||
641 | $start_forward = $k + 1; |
||
642 | $value_to_add_forward = 0; |
||
643 | } |
||
644 | } |
||
645 | |||
646 | $start_diag = max( $value_to_add_backward + $start_backward, -$d ); |
||
647 | $end_diag = min( $end_backward, $d ); |
||
648 | $value_to_add_backward = 1 - $value_to_add_backward; |
||
649 | |||
650 | // compute backward furthest reaching paths |
||
651 | for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) { |
||
652 | View Code Duplication | if ( $k == $d |
|
653 | || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] ) |
||
654 | ) { |
||
655 | $x = $V1[$limit_min_1 + $k]; |
||
656 | } else { |
||
657 | $x = $V1[$limit_plus_1 + $k] - 1; |
||
658 | } |
||
659 | |||
660 | $y = $x - $k - $delta; |
||
661 | |||
662 | $snake2 = 0; |
||
663 | View Code Duplication | while ( $x > 0 && $y > 0 |
|
664 | && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1] |
||
665 | ) { |
||
666 | --$x; |
||
667 | --$y; |
||
668 | ++$snake2; |
||
669 | } |
||
670 | $V1[$limit + $k] = $x; |
||
671 | |||
672 | if ( $k >= -$delta - $d && $k <= $d - $delta |
||
673 | && $x <= $V0[$limit + $k + $delta] |
||
674 | ) { |
||
675 | $snake0 = $bottoml1 + $x; |
||
676 | $snake1 = $bottoml2 + $y; |
||
677 | |||
678 | return 2 * $d; |
||
679 | } |
||
680 | |||
681 | // check to see if we can cut down our diagonal range |
||
682 | View Code Duplication | if ( $x <= 0 ) { |
|
683 | $start_backward = $k + 1; |
||
684 | $value_to_add_backward = 0; |
||
685 | } elseif ( $y <= 0 && $end_backward > $k - 1 ) { |
||
686 | $end_backward = $k - 1; |
||
687 | } |
||
688 | } |
||
689 | } |
||
690 | } |
||
691 | /* |
||
692 | * computing the true LCS is too expensive, instead find the diagonal |
||
693 | * with the most progress and pretend a midle snake of length 0 occurs |
||
694 | * there. |
||
695 | */ |
||
696 | |||
697 | $most_progress = self::findMostProgress( $M, $N, $limit, $V ); |
||
698 | |||
699 | $snake0 = $bottoml1 + $most_progress[0]; |
||
700 | $snake1 = $bottoml2 + $most_progress[1]; |
||
701 | $snake2 = 0; |
||
702 | wfDebug( "Computing the LCS is too expensive. Using a heuristic.\n" ); |
||
703 | $this->heuristicUsed = true; |
||
704 | |||
705 | return 5; /* |
||
706 | * HACK: since we didn't really finish the LCS computation |
||
707 | * we don't really know the length of the SES. We don't do |
||
708 | * anything with the result anyway, unless it's <=1. We know |
||
709 | * for a fact SES > 1 so 5 is as good a number as any to |
||
710 | * return here |
||
711 | */ |
||
712 | } |
||
713 | |||
714 | private static function findMostProgress( $M, $N, $limit, $V ) { |
||
715 | $delta = $N - $M; |
||
716 | |||
717 | if ( ( $M & 1 ) == ( $limit & 1 ) ) { |
||
718 | $forward_start_diag = max( -$M, -$limit ); |
||
719 | } else { |
||
720 | $forward_start_diag = max( 1 - $M, -$limit ); |
||
721 | } |
||
722 | |||
723 | $forward_end_diag = min( $N, $limit ); |
||
724 | |||
725 | if ( ( $N & 1 ) == ( $limit & 1 ) ) { |
||
726 | $backward_start_diag = max( -$N, -$limit ); |
||
727 | } else { |
||
728 | $backward_start_diag = max( 1 - $N, -$limit ); |
||
729 | } |
||
730 | |||
731 | $backward_end_diag = -min( $M, $limit ); |
||
732 | |||
733 | $temp = [ 0, 0, 0 ]; |
||
734 | |||
735 | $max_progress = array_fill( 0, ceil( max( $forward_end_diag - $forward_start_diag, |
||
736 | $backward_end_diag - $backward_start_diag ) / 2 ), $temp ); |
||
737 | $num_progress = 0; // the 1st entry is current, it is initialized |
||
738 | // with 0s |
||
739 | |||
740 | // first search the forward diagonals |
||
741 | for ( $k = $forward_start_diag; $k <= $forward_end_diag; $k += 2 ) { |
||
742 | $x = $V[0][$limit + $k]; |
||
743 | $y = $x - $k; |
||
744 | if ( $x > $N || $y > $M ) { |
||
745 | continue; |
||
746 | } |
||
747 | |||
748 | $progress = $x + $y; |
||
749 | View Code Duplication | if ( $progress > $max_progress[0][2] ) { |
|
750 | $num_progress = 0; |
||
751 | $max_progress[0][0] = $x; |
||
752 | $max_progress[0][1] = $y; |
||
753 | $max_progress[0][2] = $progress; |
||
754 | } elseif ( $progress == $max_progress[0][2] ) { |
||
755 | ++$num_progress; |
||
756 | $max_progress[$num_progress][0] = $x; |
||
757 | $max_progress[$num_progress][1] = $y; |
||
758 | $max_progress[$num_progress][2] = $progress; |
||
759 | } |
||
760 | } |
||
761 | |||
762 | $max_progress_forward = true; // initially the maximum |
||
763 | // progress is in the forward |
||
764 | // direction |
||
765 | |||
766 | // now search the backward diagonals |
||
767 | for ( $k = $backward_start_diag; $k <= $backward_end_diag; $k += 2 ) { |
||
768 | $x = $V[1][$limit + $k]; |
||
769 | $y = $x - $k - $delta; |
||
770 | if ( $x < 0 || $y < 0 ) { |
||
771 | continue; |
||
772 | } |
||
773 | |||
774 | $progress = $N - $x + $M - $y; |
||
775 | View Code Duplication | if ( $progress > $max_progress[0][2] ) { |
|
776 | $num_progress = 0; |
||
777 | $max_progress_forward = false; |
||
778 | $max_progress[0][0] = $x; |
||
779 | $max_progress[0][1] = $y; |
||
780 | $max_progress[0][2] = $progress; |
||
781 | } elseif ( $progress == $max_progress[0][2] && !$max_progress_forward ) { |
||
782 | ++$num_progress; |
||
783 | $max_progress[$num_progress][0] = $x; |
||
784 | $max_progress[$num_progress][1] = $y; |
||
785 | $max_progress[$num_progress][2] = $progress; |
||
786 | } |
||
787 | } |
||
788 | |||
789 | // return the middle diagonal with maximal progress. |
||
790 | return $max_progress[(int)floor( $num_progress / 2 )]; |
||
791 | } |
||
792 | |||
793 | /** |
||
794 | * @return mixed |
||
795 | */ |
||
796 | public function getLcsLength() { |
||
797 | if ( $this->heuristicUsed && !$this->lcsLengthCorrectedForHeuristic ) { |
||
798 | $this->lcsLengthCorrectedForHeuristic = true; |
||
799 | $this->length = $this->m - array_sum( $this->added ); |
||
800 | } |
||
801 | |||
802 | return $this->length; |
||
803 | } |
||
804 | |||
805 | } |
||
806 | |||
807 | /** |
||
808 | * Alternative representation of a set of changes, by the index |
||
809 | * ranges that are changed. |
||
810 | * |
||
811 | * @ingroup DifferenceEngine |
||
812 | */ |
||
813 | class RangeDifference { |
||
814 | |||
815 | /** @var int */ |
||
816 | public $leftstart; |
||
817 | |||
818 | /** @var int */ |
||
819 | public $leftend; |
||
820 | |||
821 | /** @var int */ |
||
822 | public $leftlength; |
||
823 | |||
824 | /** @var int */ |
||
825 | public $rightstart; |
||
826 | |||
827 | /** @var int */ |
||
828 | public $rightend; |
||
829 | |||
830 | /** @var int */ |
||
831 | public $rightlength; |
||
832 | |||
833 | function __construct( $leftstart, $leftend, $rightstart, $rightend ) { |
||
834 | $this->leftstart = $leftstart; |
||
835 | $this->leftend = $leftend; |
||
836 | $this->leftlength = $leftend - $leftstart; |
||
0 ignored issues
–
show
|
|||
837 | $this->rightstart = $rightstart; |
||
838 | $this->rightend = $rightend; |
||
839 | $this->rightlength = $rightend - $rightstart; |
||
0 ignored issues
–
show
It seems like
$rightend - $rightstart can also be of type double . However, the property $rightlength is declared as type integer . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
840 | } |
||
841 | |||
842 | } |
||
843 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.