r29569 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r29568‎ | r29569 | r29570 >
Date:03:25, 11 January 2008
Author:tstarling
Status:old
Tags:
Comment:
* Fixed template loop check, broken by changes in parse order
* Added template recursion depth limit. It needs a small limit, because of exorbitant stack space usage, xdebug compatibility problems, and the potential for O(N^2) memory usage in the template loop check.
* Made these two error messages more obvious in the parser output, with <span class="error"> instead of a comment. This is similar to the #expr error messages, which seem to have been well received by our template programmer community.
Modified paths:
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/Parser.php (modified) (history)
  • /trunk/phase3/includes/ParserOptions.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/Parser.php
@@ -87,9 +87,7 @@
8888 var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
8989 var $mInterwikiLinkHolders, $mLinkHolders;
9090 var $mIncludeSizes, $mPPNodeCount, $mDefaultSort;
91 - var $mTplExpandCache,// empty-frame expansion cache
92 - $mTemplatePath; // stores an unsorted hash of all the templates already loaded
93 - // in this path. Used for loop detection.
 91+ var $mTplExpandCache; // empty-frame expansion cache
9492 var $mTplRedirCache, $mTplDomCache, $mHeadings;
9593
9694 # Temporary
@@ -225,7 +223,6 @@
226224 $this->mUniqPrefix = "\x7fUNIQ" . Parser::getRandomString();
227225
228226 # Clear these on every parse, bug 4549
229 - $this->mTemplatePath = array();
230227 $this->mTplExpandCache = $this->mTplRedirCache = $this->mTplDomCache = array();
231228
232229 $this->mShowToc = true;
@@ -3277,9 +3274,6 @@
32783275 }
32793276 wfProfileOut( __METHOD__.'-modifiers' );
32803277
3281 - # Save path level before recursing into functions & templates.
3282 - $lastPathLevel = $this->mTemplatePath;
3283 -
32843278 # Parser functions
32853279 if ( !$found ) {
32863280 wfProfileIn( __METHOD__ . '-pfunc' );
@@ -3357,11 +3351,17 @@
33583352 $wgContLang->findVariantLink($part1, $title);
33593353 }
33603354 # Do infinite loop check
3361 - if ( isset( $this->mTemplatePath[$titleText] ) ) {
 3355+ if ( isset( $frame->loopCheckHash[$titleText] ) ) {
33623356 $found = true;
3363 - $text = "[[$part1]]" . $this->insertStripItem( '<!-- WARNING: template loop detected -->' );
 3357+ $text = "<span class=\"error\">Template loop detected: [[$titleText]]</span>";
33643358 wfDebug( __METHOD__.": template loop broken at '$titleText'\n" );
33653359 }
 3360+ # Do recursion depth check
 3361+ $limit = $this->mOptions->getMaxTemplateDepth();
 3362+ if ( $frame->depth >= $limit ) {
 3363+ $found = true;
 3364+ $text = "<span class=\"error\">Template recursion depth limit exceeded ($limit)</span>";
 3365+ }
33663366 }
33673367 }
33683368
@@ -3412,8 +3412,6 @@
34133413 # Recover the source wikitext and return it
34143414 if ( !$found ) {
34153415 $text = '{{' . $frame->implode( '|', $titleWithSpaces, $args ) . '}}';
3416 - # Prune lower levels off the recursion check path
3417 - $this->mTemplatePath = $lastPathLevel;
34183416 wfProfileOut( $fname );
34193417 return $text;
34203418 }
@@ -3422,8 +3420,8 @@
34233421 if ( $isDOM ) {
34243422 # Clean up argument array
34253423 $newFrame = $frame->newChild( $args, $title );
3426 - # Add a new element to the templace recursion path
3427 - $this->mTemplatePath[$titleText] = 1;
 3424+ # Add a new element to the templace loop detection hashtable
 3425+ $newFrame->loopCheckHash[$titleText] = true;
34283426
34293427 if ( $titleText !== false && count( $newFrame->args ) == 0 ) {
34303428 # Expansion is eligible for the empty-frame cache
@@ -3434,6 +3432,7 @@
34353433 $this->mTplExpandCache[$titleText] = $text;
34363434 }
34373435 } else {
 3436+ # Uncached expansion
34383437 $text = $newFrame->expand( $text );
34393438 }
34403439 }
@@ -3455,9 +3454,6 @@
34563455 $text = "\n" . $text;
34573456 }
34583457
3459 - # Prune lower levels off the recursion check path
3460 - $this->mTemplatePath = $lastPathLevel;
3461 -
34623458 if ( !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
34633459 # Error, oversize inclusion
34643460 $text = "[[$originalTitle]]" .
@@ -4356,8 +4352,6 @@
43574353 * found The text returned is valid, stop processing the template. This
43584354 * is on by default.
43594355 * nowiki Wiki markup in the return value should be escaped
4360 - * noparse Unsafe HTML tags should not be stripped, etc.
4361 - * noargs Don't replace triple-brace arguments in the return value
43624356 * isHTML The returned text is HTML, armour it against wikitext transformation
43634357 *
43644358 * @public
@@ -5327,6 +5321,17 @@
53285322 var $parser, $title;
53295323 var $titleCache;
53305324
 5325+ /**
 5326+ * Hashtable listing templates which are disallowed for expansion in this frame,
 5327+ * having been encountered previously in parent frames.
 5328+ */
 5329+ var $loopCheckHash;
 5330+
 5331+ /**
 5332+ * Recursion depth of this frame, top = 0
 5333+ */
 5334+ var $depth;
 5335+
53315336 const NO_ARGS = 1;
53325337 const NO_TEMPLATES = 2;
53335338 const STRIP_COMMENTS = 4;
@@ -5343,6 +5348,8 @@
53445349 $this->parser = $parser;
53455350 $this->title = $parser->mTitle;
53465351 $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false );
 5352+ $this->loopCheckHash = array();
 5353+ $this->depth = 0;
53475354 }
53485355
53495356 /**
@@ -5586,8 +5593,7 @@
55875594 * Expansion frame with template arguments
55885595 */
55895596 class PPTemplateFrame extends PPFrame {
5590 - var $parser, $args, $parent;
5591 - var $titleCache;
 5597+ var $args, $parent;
55925598
55935599 function __construct( $parser, $parent = false, $args = array(), $title = false ) {
55945600 $this->parser = $parser;
@@ -5596,6 +5602,8 @@
55975603 $this->title = $title;
55985604 $this->titleCache = $parent->titleCache;
55995605 $this->titleCache[] = $title ? $title->getPrefixedDBkey() : false;
 5606+ $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
 5607+ $this->depth = $parent->depth + 1;
56005608 }
56015609
56025610 function __toString() {
Index: trunk/phase3/includes/DefaultSettings.php
@@ -891,6 +891,14 @@
892892
893893 $wgMaxPPNodeCount = 1000000; # A complexity limit on template expansion
894894
 895+/**
 896+ * Maximum recursion depth for templates within templates.
 897+ * The current parser adds two levels to the PHP call stack for each template,
 898+ * and xdebug limits the call stack to 100 by default. So this should hopefully
 899+ * stop the parser before it hits the xdebug limit.
 900+ */
 901+$wgMaxTemplateDepth = 40;
 902+
895903 $wgExtraSubtitle = '';
896904 $wgSiteSupportPage = ''; # A page where you users can receive donations
897905
@@ -2863,4 +2871,4 @@
28642872 * $wgExceptionHooks[] = array( $class, $funcname )
28652873 * Hooks should return strings or false
28662874 */
2867 -$wgExceptionHooks = array();
\ No newline at end of file
 2875+$wgExceptionHooks = array();
Index: trunk/phase3/includes/ParserOptions.php
@@ -22,6 +22,7 @@
2323 var $mInterfaceMessage; # Which lang to call for PLURAL and GRAMMAR
2424 var $mMaxIncludeSize; # Maximum size of template expansions, in bytes
2525 var $mMaxPPNodeCount; # Maximum number of nodes touched by PPFrame::expand()
 26+ var $mMaxTemplateDepth; # Maximum recursion depth for templates within templates
2627 var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
2728 var $mTemplateCallback; # Callback for template fetching
2829 var $mEnableLimitReport; # Enable limit report in an HTML comment on output
@@ -40,6 +41,7 @@
4142 function getInterfaceMessage() { return $this->mInterfaceMessage; }
4243 function getMaxIncludeSize() { return $this->mMaxIncludeSize; }
4344 function getMaxPPNodeCount() { return $this->mMaxPPNodeCount; }
 45+ function getMaxTemplateDepth() { return $this->mMaxTemplateDepth; }
4446 function getRemoveComments() { return $this->mRemoveComments; }
4547 function getTemplateCallback() { return $this->mTemplateCallback; }
4648 function getEnableLimitReport() { return $this->mEnableLimitReport; }
@@ -72,6 +74,7 @@
7375 function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); }
7476 function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); }
7577 function setMaxPPNodeCount( $x ) { return wfSetVar( $this->mMaxPPNodeCount, $x ); }
 78+ function setMaxTemplateDepth( $x ) { return wfSetVar( $this->mMaxTemplateDepth, $x ); }
7679 function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); }
7780 function setTemplateCallback( $x ) { return wfSetVar( $this->mTemplateCallback, $x ); }
7881 function enableLimitReport( $x = true ) { return wfSetVar( $this->mEnableLimitReport, $x ); }
@@ -92,7 +95,7 @@
9396 function initialiseFromUser( $userInput ) {
9497 global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
9598 global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion, $wgMaxArticleSize;
96 - global $wgMaxPPNodeCount;
 99+ global $wgMaxPPNodeCount, $wgMaxTemplateDepth;
97100 $fname = 'ParserOptions::initialiseFromUser';
98101 wfProfileIn( $fname );
99102 if ( !$userInput ) {
@@ -122,6 +125,7 @@
123126 $this->mInterfaceMessage = false;
124127 $this->mMaxIncludeSize = $wgMaxArticleSize * 1024;
125128 $this->mMaxPPNodeCount = $wgMaxPPNodeCount;
 129+ $this->mMaxTemplateDepth = $wgMaxTemplateDepth;
126130 $this->mRemoveComments = true;
127131 $this->mTemplateCallback = array( 'Parser', 'statelessFetchTemplate' );
128132 $this->mEnableLimitReport = false;

Status & tagging log