diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -1176,6 +1176,16 @@ $added = array_map('trim', $hunk->getAddedLines()); for (reset($added); list($line, $code) = each($added); ) { if (isset($map[$code])) { // We found a long matching line. + + if (count($map[$code]) > 16) { + // If there are a large number of identical lines in this diff, + // don't try to figure out where this block came from: the + // analysis is O(N^2), since we need to compare every line + // against every other line. Even if we arrive at a result, it + // is unlikely to be meaningful. See T5041. + continue 2; + } + $best_length = 0; foreach ($map[$code] as $val) { // Explore all candidates. list($file, $orig_line) = $val; diff --git a/src/applications/differential/storage/__tests__/DifferentialDiffTestCase.php b/src/applications/differential/storage/__tests__/DifferentialDiffTestCase.php --- a/src/applications/differential/storage/__tests__/DifferentialDiffTestCase.php +++ b/src/applications/differential/storage/__tests__/DifferentialDiffTestCase.php @@ -15,4 +15,41 @@ ipull($copies, 1)); } + public function testDetectSlowCopiedCode() { + // This tests that the detector has a reasonable runtime when a diff + // contains a very large number of identical lines. See T5041. + + $parser = new ArcanistDiffParser(); + + $line = str_repeat('x', 60); + $oline = '-'.$line."\n"; + $nline = '+'.$line."\n"; + + $n = 1000; + $oblock = str_repeat($oline, $n); + $nblock = str_repeat($nline, $n); + + $raw_diff = <<<EODIFF +diff --git a/dst b/dst +new file mode 100644 +index 0000000..1234567 +--- /dev/null ++++ b/dst +@@ -0,0 +1,{$n} @@ +{$nblock} +diff --git a/src b/src +deleted file mode 100644 +index 123457..0000000 +--- a/src ++++ /dev/null +@@ -1,{$n} +0,0 @@ +{$oblock} +EODIFF; + + $diff = DifferentialDiff::newFromRawChanges($parser->parseDiff($raw_diff)); + + $this->assertTrue(true); + } + + }