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 MapCacheLRU; |
32 | use MediaWiki\Cache\CacheKeyHelper; |
33 | use MediaWiki\Content\Content; |
34 | use MediaWiki\Page\PageReference; |
35 | use MediaWiki\Title\Title; |
36 | use Psr\Log\LoggerInterface; |
37 | use RuntimeException; |
38 | |
39 | /** |
40 | * For observing and detecting parser behaviors, such as duplicate parses |
41 | * @internal |
42 | * @package MediaWiki\Parser |
43 | */ |
44 | class ParserObserver { |
45 | /** |
46 | * @var LoggerInterface |
47 | */ |
48 | private $logger; |
49 | |
50 | private MapCacheLRU $previousParseStackTraces; |
51 | |
52 | /** |
53 | * @param LoggerInterface $logger |
54 | */ |
55 | public function __construct( LoggerInterface $logger ) { |
56 | $this->logger = $logger; |
57 | $this->previousParseStackTraces = new MapCacheLRU( 10 ); |
58 | } |
59 | |
60 | /** |
61 | * @param PageReference $page |
62 | * @param int|null $revId |
63 | * @param ParserOptions $options |
64 | * @param Content $content |
65 | * @param ParserOutput $output |
66 | */ |
67 | public function notifyParse( |
68 | PageReference $page, ?int $revId, ParserOptions $options, Content $content, ParserOutput $output |
69 | ) { |
70 | $pageKey = CacheKeyHelper::getKeyForPage( $page ); |
71 | |
72 | $optionsHash = $options->optionsHash( |
73 | $output->getUsedOptions(), |
74 | Title::newFromPageReference( $page ) |
75 | ); |
76 | |
77 | $contentStr = $content->isValid() ? $content->serialize() : null; |
78 | // $contentStr may be null if the content could not be serialized |
79 | $contentSha1 = $contentStr ? sha1( $contentStr ) : 'INVALID'; |
80 | |
81 | $index = $this->getParseId( $pageKey, $revId, $optionsHash, $contentSha1 ); |
82 | |
83 | $stackTrace = ( new RuntimeException() )->getTraceAsString(); |
84 | if ( $this->previousParseStackTraces->has( $index ) ) { |
85 | |
86 | // NOTE: there may be legitimate changes to re-parse the same WikiText content, |
87 | // e.g. if predicted revision ID for the REVISIONID magic word mismatched. |
88 | // But that should be rare. |
89 | $this->logger->debug( |
90 | __METHOD__ . ': Possibly redundant parse!', |
91 | [ |
92 | 'page' => $pageKey, |
93 | 'rev' => $revId, |
94 | 'options-hash' => $optionsHash, |
95 | 'contentSha1' => $contentSha1, |
96 | 'trace' => $stackTrace, |
97 | 'previous-trace' => $this->previousParseStackTraces->get( $index ), |
98 | ] |
99 | ); |
100 | } |
101 | $this->previousParseStackTraces->set( $index, $stackTrace ); |
102 | } |
103 | |
104 | /** |
105 | * @param string $titleStr |
106 | * @param int|null $revId |
107 | * @param string $optionsHash |
108 | * @param string $contentSha1 |
109 | * @return string |
110 | */ |
111 | private function getParseId( string $titleStr, ?int $revId, string $optionsHash, string $contentSha1 ): string { |
112 | // $revId may be null when previewing a new page |
113 | $revIdStr = $revId ?? ""; |
114 | |
115 | return "$titleStr.$revIdStr.$optionsHash.$contentSha1"; |
116 | } |
117 | |
118 | } |