Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
26 / 26 |
|
100.00% |
3 / 3 |
CRAP | |
100.00% |
1 / 1 |
ParserObserver | |
100.00% |
26 / 26 |
|
100.00% |
3 / 3 |
6 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
notifyParse | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
4 | |||
getParseId | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | /** |
4 | * Observer to detect parser behaviors such as duplicate parses |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation; either version 2 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License along |
17 | * with this program; if not, write to the Free Software Foundation, Inc., |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | * http://www.gnu.org/copyleft/gpl.html |
20 | * |
21 | * @since 1.38 |
22 | * |
23 | * @file |
24 | * @ingroup Parser |
25 | * |
26 | * @author Cindy Cicalese |
27 | */ |
28 | |
29 | namespace MediaWiki\Parser; |
30 | |
31 | use Content; |
32 | use MapCacheLRU; |
33 | use MediaWiki\Cache\CacheKeyHelper; |
34 | use MediaWiki\Page\PageReference; |
35 | use MediaWiki\Title\Title; |
36 | use ParserOptions; |
37 | use Psr\Log\LoggerInterface; |
38 | use RuntimeException; |
39 | |
40 | /** |
41 | * For observing and detecting parser behaviors, such as duplicate parses |
42 | * @internal |
43 | * @package MediaWiki\Parser |
44 | */ |
45 | class ParserObserver { |
46 | /** |
47 | * @var LoggerInterface |
48 | */ |
49 | private $logger; |
50 | |
51 | private MapCacheLRU $previousParseStackTraces; |
52 | |
53 | /** |
54 | * @param LoggerInterface $logger |
55 | */ |
56 | public function __construct( LoggerInterface $logger ) { |
57 | $this->logger = $logger; |
58 | $this->previousParseStackTraces = new MapCacheLRU( 10 ); |
59 | } |
60 | |
61 | /** |
62 | * @param PageReference $page |
63 | * @param int|null $revId |
64 | * @param ParserOptions $options |
65 | * @param Content $content |
66 | * @param ParserOutput $output |
67 | */ |
68 | public function notifyParse( |
69 | PageReference $page, ?int $revId, ParserOptions $options, Content $content, ParserOutput $output |
70 | ) { |
71 | $pageKey = CacheKeyHelper::getKeyForPage( $page ); |
72 | |
73 | $optionsHash = $options->optionsHash( |
74 | $output->getUsedOptions(), |
75 | Title::newFromPageReference( $page ) |
76 | ); |
77 | |
78 | $contentStr = $content->isValid() ? $content->serialize() : null; |
79 | // $contentStr may be null if the content could not be serialized |
80 | $contentSha1 = $contentStr ? sha1( $contentStr ) : 'INVALID'; |
81 | |
82 | $index = $this->getParseId( $pageKey, $revId, $optionsHash, $contentSha1 ); |
83 | |
84 | $stackTrace = ( new RuntimeException() )->getTraceAsString(); |
85 | if ( $this->previousParseStackTraces->has( $index ) ) { |
86 | |
87 | // NOTE: there may be legitimate changes to re-parse the same WikiText content, |
88 | // e.g. if predicted revision ID for the REVISIONID magic word mismatched. |
89 | // But that should be rare. |
90 | $this->logger->debug( |
91 | __METHOD__ . ': Possibly redundant parse!', |
92 | [ |
93 | 'page' => $pageKey, |
94 | 'rev' => $revId, |
95 | 'options-hash' => $optionsHash, |
96 | 'contentSha1' => $contentSha1, |
97 | 'trace' => $stackTrace, |
98 | 'previous-trace' => $this->previousParseStackTraces->get( $index ), |
99 | ] |
100 | ); |
101 | } |
102 | $this->previousParseStackTraces->set( $index, $stackTrace ); |
103 | } |
104 | |
105 | /** |
106 | * @param string $titleStr |
107 | * @param int|null $revId |
108 | * @param string $optionsHash |
109 | * @param string $contentSha1 |
110 | * @return string |
111 | */ |
112 | private function getParseId( string $titleStr, ?int $revId, string $optionsHash, string $contentSha1 ): string { |
113 | // $revId may be null when previewing a new page |
114 | $revIdStr = $revId ?? ""; |
115 | |
116 | return "$titleStr.$revIdStr.$optionsHash.$contentSha1"; |
117 | } |
118 | |
119 | } |