Index: trunk/phase3/includes/Parser.php |
— | — | @@ -87,9 +87,7 @@ |
88 | 88 | var $mIncludeCount, $mArgStack, $mLastSection, $mInPre; |
89 | 89 | var $mInterwikiLinkHolders, $mLinkHolders; |
90 | 90 | 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 |
94 | 92 | var $mTplRedirCache, $mTplDomCache, $mHeadings; |
95 | 93 | |
96 | 94 | # Temporary |
— | — | @@ -225,7 +223,6 @@ |
226 | 224 | $this->mUniqPrefix = "\x7fUNIQ" . Parser::getRandomString(); |
227 | 225 | |
228 | 226 | # Clear these on every parse, bug 4549 |
229 | | - $this->mTemplatePath = array(); |
230 | 227 | $this->mTplExpandCache = $this->mTplRedirCache = $this->mTplDomCache = array(); |
231 | 228 | |
232 | 229 | $this->mShowToc = true; |
— | — | @@ -3277,9 +3274,6 @@ |
3278 | 3275 | } |
3279 | 3276 | wfProfileOut( __METHOD__.'-modifiers' ); |
3280 | 3277 | |
3281 | | - # Save path level before recursing into functions & templates. |
3282 | | - $lastPathLevel = $this->mTemplatePath; |
3283 | | - |
3284 | 3278 | # Parser functions |
3285 | 3279 | if ( !$found ) { |
3286 | 3280 | wfProfileIn( __METHOD__ . '-pfunc' ); |
— | — | @@ -3357,11 +3351,17 @@ |
3358 | 3352 | $wgContLang->findVariantLink($part1, $title); |
3359 | 3353 | } |
3360 | 3354 | # Do infinite loop check |
3361 | | - if ( isset( $this->mTemplatePath[$titleText] ) ) { |
| 3355 | + if ( isset( $frame->loopCheckHash[$titleText] ) ) { |
3362 | 3356 | $found = true; |
3363 | | - $text = "[[$part1]]" . $this->insertStripItem( '<!-- WARNING: template loop detected -->' ); |
| 3357 | + $text = "<span class=\"error\">Template loop detected: [[$titleText]]</span>"; |
3364 | 3358 | wfDebug( __METHOD__.": template loop broken at '$titleText'\n" ); |
3365 | 3359 | } |
| 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 | + } |
3366 | 3366 | } |
3367 | 3367 | } |
3368 | 3368 | |
— | — | @@ -3412,8 +3412,6 @@ |
3413 | 3413 | # Recover the source wikitext and return it |
3414 | 3414 | if ( !$found ) { |
3415 | 3415 | $text = '{{' . $frame->implode( '|', $titleWithSpaces, $args ) . '}}'; |
3416 | | - # Prune lower levels off the recursion check path |
3417 | | - $this->mTemplatePath = $lastPathLevel; |
3418 | 3416 | wfProfileOut( $fname ); |
3419 | 3417 | return $text; |
3420 | 3418 | } |
— | — | @@ -3422,8 +3420,8 @@ |
3423 | 3421 | if ( $isDOM ) { |
3424 | 3422 | # Clean up argument array |
3425 | 3423 | $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; |
3428 | 3426 | |
3429 | 3427 | if ( $titleText !== false && count( $newFrame->args ) == 0 ) { |
3430 | 3428 | # Expansion is eligible for the empty-frame cache |
— | — | @@ -3434,6 +3432,7 @@ |
3435 | 3433 | $this->mTplExpandCache[$titleText] = $text; |
3436 | 3434 | } |
3437 | 3435 | } else { |
| 3436 | + # Uncached expansion |
3438 | 3437 | $text = $newFrame->expand( $text ); |
3439 | 3438 | } |
3440 | 3439 | } |
— | — | @@ -3455,9 +3454,6 @@ |
3456 | 3455 | $text = "\n" . $text; |
3457 | 3456 | } |
3458 | 3457 | |
3459 | | - # Prune lower levels off the recursion check path |
3460 | | - $this->mTemplatePath = $lastPathLevel; |
3461 | | - |
3462 | 3458 | if ( !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) { |
3463 | 3459 | # Error, oversize inclusion |
3464 | 3460 | $text = "[[$originalTitle]]" . |
— | — | @@ -4356,8 +4352,6 @@ |
4357 | 4353 | * found The text returned is valid, stop processing the template. This |
4358 | 4354 | * is on by default. |
4359 | 4355 | * 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 |
4362 | 4356 | * isHTML The returned text is HTML, armour it against wikitext transformation |
4363 | 4357 | * |
4364 | 4358 | * @public |
— | — | @@ -5327,6 +5321,17 @@ |
5328 | 5322 | var $parser, $title; |
5329 | 5323 | var $titleCache; |
5330 | 5324 | |
| 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 | + |
5331 | 5336 | const NO_ARGS = 1; |
5332 | 5337 | const NO_TEMPLATES = 2; |
5333 | 5338 | const STRIP_COMMENTS = 4; |
— | — | @@ -5343,6 +5348,8 @@ |
5344 | 5349 | $this->parser = $parser; |
5345 | 5350 | $this->title = $parser->mTitle; |
5346 | 5351 | $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false ); |
| 5352 | + $this->loopCheckHash = array(); |
| 5353 | + $this->depth = 0; |
5347 | 5354 | } |
5348 | 5355 | |
5349 | 5356 | /** |
— | — | @@ -5586,8 +5593,7 @@ |
5587 | 5594 | * Expansion frame with template arguments |
5588 | 5595 | */ |
5589 | 5596 | class PPTemplateFrame extends PPFrame { |
5590 | | - var $parser, $args, $parent; |
5591 | | - var $titleCache; |
| 5597 | + var $args, $parent; |
5592 | 5598 | |
5593 | 5599 | function __construct( $parser, $parent = false, $args = array(), $title = false ) { |
5594 | 5600 | $this->parser = $parser; |
— | — | @@ -5596,6 +5602,8 @@ |
5597 | 5603 | $this->title = $title; |
5598 | 5604 | $this->titleCache = $parent->titleCache; |
5599 | 5605 | $this->titleCache[] = $title ? $title->getPrefixedDBkey() : false; |
| 5606 | + $this->loopCheckHash = /*clone*/ $parent->loopCheckHash; |
| 5607 | + $this->depth = $parent->depth + 1; |
5600 | 5608 | } |
5601 | 5609 | |
5602 | 5610 | function __toString() { |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -891,6 +891,14 @@ |
892 | 892 | |
893 | 893 | $wgMaxPPNodeCount = 1000000; # A complexity limit on template expansion |
894 | 894 | |
| 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 | + |
895 | 903 | $wgExtraSubtitle = ''; |
896 | 904 | $wgSiteSupportPage = ''; # A page where you users can receive donations |
897 | 905 | |
— | — | @@ -2863,4 +2871,4 @@ |
2864 | 2872 | * $wgExceptionHooks[] = array( $class, $funcname ) |
2865 | 2873 | * Hooks should return strings or false |
2866 | 2874 | */ |
2867 | | -$wgExceptionHooks = array(); |
\ No newline at end of file |
| 2875 | +$wgExceptionHooks = array(); |
Index: trunk/phase3/includes/ParserOptions.php |
— | — | @@ -22,6 +22,7 @@ |
23 | 23 | var $mInterfaceMessage; # Which lang to call for PLURAL and GRAMMAR |
24 | 24 | var $mMaxIncludeSize; # Maximum size of template expansions, in bytes |
25 | 25 | var $mMaxPPNodeCount; # Maximum number of nodes touched by PPFrame::expand() |
| 26 | + var $mMaxTemplateDepth; # Maximum recursion depth for templates within templates |
26 | 27 | var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS |
27 | 28 | var $mTemplateCallback; # Callback for template fetching |
28 | 29 | var $mEnableLimitReport; # Enable limit report in an HTML comment on output |
— | — | @@ -40,6 +41,7 @@ |
41 | 42 | function getInterfaceMessage() { return $this->mInterfaceMessage; } |
42 | 43 | function getMaxIncludeSize() { return $this->mMaxIncludeSize; } |
43 | 44 | function getMaxPPNodeCount() { return $this->mMaxPPNodeCount; } |
| 45 | + function getMaxTemplateDepth() { return $this->mMaxTemplateDepth; } |
44 | 46 | function getRemoveComments() { return $this->mRemoveComments; } |
45 | 47 | function getTemplateCallback() { return $this->mTemplateCallback; } |
46 | 48 | function getEnableLimitReport() { return $this->mEnableLimitReport; } |
— | — | @@ -72,6 +74,7 @@ |
73 | 75 | function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); } |
74 | 76 | function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); } |
75 | 77 | function setMaxPPNodeCount( $x ) { return wfSetVar( $this->mMaxPPNodeCount, $x ); } |
| 78 | + function setMaxTemplateDepth( $x ) { return wfSetVar( $this->mMaxTemplateDepth, $x ); } |
76 | 79 | function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); } |
77 | 80 | function setTemplateCallback( $x ) { return wfSetVar( $this->mTemplateCallback, $x ); } |
78 | 81 | function enableLimitReport( $x = true ) { return wfSetVar( $this->mEnableLimitReport, $x ); } |
— | — | @@ -92,7 +95,7 @@ |
93 | 96 | function initialiseFromUser( $userInput ) { |
94 | 97 | global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages; |
95 | 98 | global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion, $wgMaxArticleSize; |
96 | | - global $wgMaxPPNodeCount; |
| 99 | + global $wgMaxPPNodeCount, $wgMaxTemplateDepth; |
97 | 100 | $fname = 'ParserOptions::initialiseFromUser'; |
98 | 101 | wfProfileIn( $fname ); |
99 | 102 | if ( !$userInput ) { |
— | — | @@ -122,6 +125,7 @@ |
123 | 126 | $this->mInterfaceMessage = false; |
124 | 127 | $this->mMaxIncludeSize = $wgMaxArticleSize * 1024; |
125 | 128 | $this->mMaxPPNodeCount = $wgMaxPPNodeCount; |
| 129 | + $this->mMaxTemplateDepth = $wgMaxTemplateDepth; |
126 | 130 | $this->mRemoveComments = true; |
127 | 131 | $this->mTemplateCallback = array( 'Parser', 'statelessFetchTemplate' ); |
128 | 132 | $this->mEnableLimitReport = false; |