Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/diff/DiffEngine.php (2 issues)

Upgrade to new PHP Analysis Engine

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
Documentation Bug introduced by
It seems like $leftend - $leftstart can also be of type double. However, the property $leftlength 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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account 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.

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;
}
Loading history...
837
		$this->rightstart = $rightstart;
838
		$this->rightend = $rightend;
839
		$this->rightlength = $rightend - $rightstart;
0 ignored issues
show
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account 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.

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;
}
Loading history...
840
	}
841
842
}
843