* Factored out PPD "part" handling into its own class. Verified with differential fuzz test.

* Stabilise timestamps generated by the parser to avoid diff test false positives
* Fixed msgnw bug. Use RECOVER_ORIG.
* Fixed editintro bug. Cloning the parser in MessageCache has some side-effects that need to be corrected.
* Fixed typo in Parser_DiffTest.php
* General improvements to preprocessorFuzzTest.php
* Fixed breakage of XML output feature in Special:ExpandTemplates
This commit is contained in:
Tim Starling 2008-01-24 04:29:56 +00:00
parent 02f55026d2
commit 906e978e91
5 changed files with 328 additions and 167 deletions

View file

@ -89,7 +89,7 @@ class Parser
# Persistent:
var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables,
$mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerSuffix,
$mExtLinkBracketedRegex, $mPreprocessor, $mDefaultStripList;
$mExtLinkBracketedRegex, $mPreprocessor, $mDefaultStripList, $mVarCache, $mConf;
# Cleared with clearState():
@ -118,6 +118,7 @@ class Parser
* @public
*/
function __construct( $conf = array() ) {
$this->mConf = $conf;
$this->mTagHooks = array();
$this->mTransparentTagHooks = array();
$this->mFunctionHooks = array();
@ -126,6 +127,7 @@ class Parser
$this->mMarkerSuffix = "-QINU\x7f";
$this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'.
'[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x0a\\x0d]*?)\]/S';
$this->mVarCache = array();
if ( isset( $conf['preprocessorClass'] ) ) {
$this->mPreprocessorClass = $conf['preprocessorClass'];
} else {
@ -237,6 +239,7 @@ class Parser
* the behaviour of <nowiki> in a link.
*/
#$this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString();
# Changed to \x7f to allow XML double-parsing -- TS
$this->mUniqPrefix = "\x7fUNIQ" . Parser::getRandomString();
# Clear these on every parse, bug 4549
@ -252,6 +255,11 @@ class Parser
$this->mDefaultSort = false;
$this->mHeadings = array();
# Fix cloning
if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
$this->mPreprocessor = null;
}
wfRunHooks( 'ParserClearState', array( &$this ) );
wfProfileOut( __METHOD__ );
}
@ -2374,14 +2382,13 @@ class Parser
* Some of these require message or data lookups and can be
* expensive to check many times.
*/
static $varCache = array();
if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$varCache ) ) ) {
if ( isset( $varCache[$index] ) ) {
return $varCache[$index];
if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$this->mVarCache ) ) ) {
if ( isset( $this->mVarCache[$index] ) ) {
return $this->mVarCache[$index];
}
}
$ts = time();
$ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
# Use the time zone
@ -2408,29 +2415,29 @@ class Parser
switch ( $index ) {
case 'currentmonth':
return $varCache[$index] = $wgContLang->formatNum( gmdate( 'm', $ts ) );
return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'm', $ts ) );
case 'currentmonthname':
return $varCache[$index] = $wgContLang->getMonthName( gmdate( 'n', $ts ) );
return $this->mVarCache[$index] = $wgContLang->getMonthName( gmdate( 'n', $ts ) );
case 'currentmonthnamegen':
return $varCache[$index] = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) );
return $this->mVarCache[$index] = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) );
case 'currentmonthabbrev':
return $varCache[$index] = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) );
return $this->mVarCache[$index] = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) );
case 'currentday':
return $varCache[$index] = $wgContLang->formatNum( gmdate( 'j', $ts ) );
return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'j', $ts ) );
case 'currentday2':
return $varCache[$index] = $wgContLang->formatNum( gmdate( 'd', $ts ) );
return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'd', $ts ) );
case 'localmonth':
return $varCache[$index] = $wgContLang->formatNum( $localMonth );
return $this->mVarCache[$index] = $wgContLang->formatNum( $localMonth );
case 'localmonthname':
return $varCache[$index] = $wgContLang->getMonthName( $localMonthName );
return $this->mVarCache[$index] = $wgContLang->getMonthName( $localMonthName );
case 'localmonthnamegen':
return $varCache[$index] = $wgContLang->getMonthNameGen( $localMonthName );
return $this->mVarCache[$index] = $wgContLang->getMonthNameGen( $localMonthName );
case 'localmonthabbrev':
return $varCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName );
return $this->mVarCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName );
case 'localday':
return $varCache[$index] = $wgContLang->formatNum( $localDay );
return $this->mVarCache[$index] = $wgContLang->formatNum( $localDay );
case 'localday2':
return $varCache[$index] = $wgContLang->formatNum( $localDay2 );
return $this->mVarCache[$index] = $wgContLang->formatNum( $localDay2 );
case 'pagename':
return wfEscapeWikiText( $this->mTitle->getText() );
case 'pagenamee':
@ -2516,51 +2523,51 @@ class Parser
case 'subjectspacee':
return( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
case 'currentdayname':
return $varCache[$index] = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 );
return $this->mVarCache[$index] = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 );
case 'currentyear':
return $varCache[$index] = $wgContLang->formatNum( gmdate( 'Y', $ts ), true );
return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'Y', $ts ), true );
case 'currenttime':
return $varCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false );
return $this->mVarCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false );
case 'currenthour':
return $varCache[$index] = $wgContLang->formatNum( gmdate( 'H', $ts ), true );
return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'H', $ts ), true );
case 'currentweek':
// @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
// int to remove the padding
return $varCache[$index] = $wgContLang->formatNum( (int)gmdate( 'W', $ts ) );
return $this->mVarCache[$index] = $wgContLang->formatNum( (int)gmdate( 'W', $ts ) );
case 'currentdow':
return $varCache[$index] = $wgContLang->formatNum( gmdate( 'w', $ts ) );
return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'w', $ts ) );
case 'localdayname':
return $varCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 );
return $this->mVarCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 );
case 'localyear':
return $varCache[$index] = $wgContLang->formatNum( $localYear, true );
return $this->mVarCache[$index] = $wgContLang->formatNum( $localYear, true );
case 'localtime':
return $varCache[$index] = $wgContLang->time( $localTimestamp, false, false );
return $this->mVarCache[$index] = $wgContLang->time( $localTimestamp, false, false );
case 'localhour':
return $varCache[$index] = $wgContLang->formatNum( $localHour, true );
return $this->mVarCache[$index] = $wgContLang->formatNum( $localHour, true );
case 'localweek':
// @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
// int to remove the padding
return $varCache[$index] = $wgContLang->formatNum( (int)$localWeek );
return $this->mVarCache[$index] = $wgContLang->formatNum( (int)$localWeek );
case 'localdow':
return $varCache[$index] = $wgContLang->formatNum( $localDayOfWeek );
return $this->mVarCache[$index] = $wgContLang->formatNum( $localDayOfWeek );
case 'numberofarticles':
return $varCache[$index] = $wgContLang->formatNum( SiteStats::articles() );
return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::articles() );
case 'numberoffiles':
return $varCache[$index] = $wgContLang->formatNum( SiteStats::images() );
return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::images() );
case 'numberofusers':
return $varCache[$index] = $wgContLang->formatNum( SiteStats::users() );
return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::users() );
case 'numberofpages':
return $varCache[$index] = $wgContLang->formatNum( SiteStats::pages() );
return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::pages() );
case 'numberofadmins':
return $varCache[$index] = $wgContLang->formatNum( SiteStats::admins() );
return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::admins() );
case 'numberofedits':
return $varCache[$index] = $wgContLang->formatNum( SiteStats::edits() );
return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::edits() );
case 'currenttimestamp':
return $varCache[$index] = wfTimestampNow();
return $this->mVarCache[$index] = wfTimestamp( TS_MW, $ts );
case 'localtimestamp':
return $varCache[$index] = $localTimestamp;
return $this->mVarCache[$index] = $localTimestamp;
case 'currentversion':
return $varCache[$index] = SpecialVersion::getVersion();
return $this->mVarCache[$index] = SpecialVersion::getVersion();
case 'sitename':
return $wgSitename;
case 'server':
@ -2576,7 +2583,7 @@ class Parser
return $wgContLanguageCode;
default:
$ret = null;
if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$varCache, &$index, &$ret ) ) )
if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret ) ) )
return $ret;
else
return null;
@ -2936,7 +2943,9 @@ class Parser
# Clean up argument array
$newFrame = $frame->newChild( $args, $title );
if ( $titleText !== false && $newFrame->isEmpty() ) {
if ( $nowiki ) {
$text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
} elseif ( $titleText !== false && $newFrame->isEmpty() ) {
# Expansion is eligible for the empty-frame cache
if ( isset( $this->mTplExpandCache[$titleText] ) ) {
$text = $this->mTplExpandCache[$titleText];
@ -2949,6 +2958,10 @@ class Parser
$text = $newFrame->expand( $text );
}
}
if ( $isLocalObj && $nowiki ) {
$text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
$isLocalObj = false;
}
# Replace raw HTML by a placeholder
# Add a blank line preceding, to prevent it from mucking up
@ -3635,7 +3648,7 @@ class Parser
$oldtz = getenv( 'TZ' );
putenv( 'TZ='.$wgLocaltimezone );
}
$d = $wgContLang->timeanddate( date( 'YmdHis' ), false, false) .
$d = $wgContLang->timeanddate( $this->mOptions->getTimestamp(), false, false) .
' (' . date( 'T' ) . ')';
if ( isset( $wgLocaltimezone ) ) {
putenv( 'TZ='.$oldtz );
@ -4776,6 +4789,9 @@ class Parser
*/
function testSrvus( $text, $title, $options, $outputType = self::OT_HTML ) {
$this->clearState();
if ( ! ( $title instanceof Title ) ) {
$title = Title::newFromText( $title );
}
$this->mTitle = $title;
$this->mOptions = $options;
$this->setOutputType( $outputType );
@ -4787,10 +4803,16 @@ class Parser
function testPst( $text, $title, $options ) {
global $wgUser;
if ( ! ( $title instanceof Title ) ) {
$title = Title::newFromText( $title );
}
return $this->preSaveTransform( $text, $title, $wgUser, $options );
}
function testPreprocess( $text, $title, $options ) {
if ( ! ( $title instanceof Title ) ) {
$title = Title::newFromText( $title );
}
return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
}
}

View file

@ -26,6 +26,7 @@ class ParserOptions
var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
var $mTemplateCallback; # Callback for template fetching
var $mEnableLimitReport; # Enable limit report in an HTML comment on output
var $mTimestamp; # Timestamp used for {{CURRENTDAY}} etc.
var $mUser; # Stored user object, just used to initialise the skin
@ -60,6 +61,13 @@ class ParserOptions
return $this->mDateFormat;
}
function getTimestamp() {
if ( !isset( $this->mTimestamp ) ) {
$this->mTimestamp = wfTimestampNow();
}
return $this->mTimestamp;
}
function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); }
function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
@ -78,6 +86,7 @@ class ParserOptions
function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); }
function setTemplateCallback( $x ) { return wfSetVar( $this->mTemplateCallback, $x ); }
function enableLimitReport( $x = true ) { return wfSetVar( $this->mEnableLimitReport, $x ); }
function setTimestamp( $x ) { return wfSetVar( $this->mTimestamp, $x ); }
function __construct( $user = null ) {
$this->initialiseFromUser( $user );

View file

@ -18,7 +18,7 @@ class Parser_DiffTest
foreach ( $this->conf['parsers'] as $i => $parserConf ) {
if ( !is_array( $parserConf ) ) {
$class = $parserConf;
$parserconf = array( 'class' => $parserConf );
$parserConf = array( 'class' => $parserConf );
} else {
$class = $parserConf['class'];
}

View file

@ -1,16 +1,37 @@
<?php
class Preprocessor_DOM implements Preprocessor {
var $parser;
var $parser, $memoryLimit;
function __construct( $parser ) {
$this->parser = $parser;
$mem = ini_get( 'memory_limit' );
$this->memoryLimit = false;
if ( strval( $mem ) !== '' && $mem != -1 ) {
if ( preg_match( '/^\d+$/', $mem ) ) {
$this->memoryLimit = $mem;
} elseif ( preg_match( '/^(\d+)M$/i', $mem, $m ) ) {
$this->memoryLimit = $m[1] * 1048576;
}
}
}
function newFrame() {
return new PPFrame_DOM( $this );
}
function memCheck() {
if ( $this->memoryLimit === false ) {
return;
}
$usage = memory_get_usage();
if ( $usage > $this->memoryLimit * 0.9 ) {
$limit = intval( $this->memoryLimit * 0.9 / 1048576 + 0.5 );
throw new MWException( "Preprocessor hit 90% memory limit ($limit MB)" );
}
return $usage <= $this->memoryLimit * 0.8;
}
/**
* Preprocess some wikitext and return the document tree.
* This is the ghost of Parser::replace_variables().
@ -78,11 +99,11 @@ class Preprocessor_DOM implements Preprocessor {
$stack = new PPDStack;
$searchBase = '[{<';
$searchBase = '[{<'; #}
$revText = strrev( $text ); // For fast reverse searches
$i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start
$accum =& $stack->getAccum(); # Current text accumulator
$accum =& $stack->getAccum(); # Current accumulator
$accum = '<root>';
$findEquals = false; # True to find equals signs in arguments
$findPipe = false; # True to take notice of pipe characters
@ -93,6 +114,8 @@ class Preprocessor_DOM implements Preprocessor {
$fakeLineStart = true; # Do a line-start run without outputting an LF character
while ( true ) {
if ( ! ($i % 10) ) $this->memCheck();
if ( $findOnlyinclude ) {
// Ignore all input up to the next <onlyinclude>
$startPos = strpos( $text, '<onlyinclude>', $i );
@ -241,6 +264,17 @@ class Preprocessor_DOM implements Preprocessor {
$endPos += 2;
}
/*
if ( $stack->top ) {
if ( $stack->top->commentEndPos !== false && $stack->top->commentEndPos == $wsStart ) {
// Comments abutting, no change in visual end
$stack->top->commentEndPos = $wsEnd;
} else {
$stack->top->visualEndPos = $wsStart;
$stack->top->commentEndPos = $wsEnd;
}
}
*/
$i = $endPos + 1;
$inner = substr( $text, $startPos, $endPos - $startPos + 1 );
$accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
@ -326,7 +360,7 @@ class Preprocessor_DOM implements Preprocessor {
$piece = array(
'open' => "\n",
'close' => "\n",
'parts' => array( str_repeat( '=', $count ) ),
'parts' => array( new PPDPart( str_repeat( '=', $count ) ) ),
'startPos' => $i,
'count' => $count );
$stack->push( $piece );
@ -395,8 +429,6 @@ class Preprocessor_DOM implements Preprocessor {
'open' => $curChar,
'close' => $rule['end'],
'count' => $count,
'parts' => array( '' ),
'eqpos' => array(),
'lineStart' => ($i > 0 && $text[$i-1] == "\n"),
);
@ -444,14 +476,12 @@ class Preprocessor_DOM implements Preprocessor {
$name = $rule['names'][$matchingCount];
if ( $name === null ) {
// No element, just literal text
$element = str_repeat( $piece->open, $matchingCount ) .
implode( '|', $piece->parts ) .
str_repeat( $rule['end'], $matchingCount );
$element = $piece->breakSyntax( $matchingCount ) . str_repeat( $rule['end'], $matchingCount );
} else {
# Create XML element
# Note: $parts is already XML, does not need to be encoded further
$parts = $piece->parts;
$title = $parts[0];
$title = $parts[0]->out;
unset( $parts[0] );
# The invocation is at the start of the line if lineStart is set in
@ -466,13 +496,12 @@ class Preprocessor_DOM implements Preprocessor {
$element .= "<title>$title</title>";
$argIndex = 1;
foreach ( $parts as $partIndex => $part ) {
if ( isset( $piece->eqpos[$partIndex] ) ) {
$eqpos = $piece->eqpos[$partIndex];
$argName = substr( $part, 0, $eqpos );
$argValue = substr( $part, $eqpos + 1 );
if ( isset( $part->eqpos ) ) {
$argName = substr( $part->out, 0, $part->eqpos );
$argValue = substr( $part->out, $part->eqpos + 1 );
$element .= "<part><name>$argName</name>=<value>$argValue</value></part>";
} else {
$element .= "<part><name index=\"$argIndex\" /><value>$part</value></part>";
$element .= "<part><name index=\"$argIndex\" /><value>{$part->out}</value></part>";
$argIndex++;
}
}
@ -488,9 +517,8 @@ class Preprocessor_DOM implements Preprocessor {
# Re-add the old stack element if it still has unmatched opening characters remaining
if ($matchingCount < $piece->count) {
$piece->parts = array( '' );
$piece->parts = array( new PPDPart );
$piece->count -= $matchingCount;
$piece->eqpos = array();
# do we still qualify for any callback with remaining count?
$names = $rules[$piece->open]['names'];
$skippedBraces = 0;
@ -515,15 +543,14 @@ class Preprocessor_DOM implements Preprocessor {
elseif ( $found == 'pipe' ) {
$findEquals = true; // shortcut for getFlags()
$stack->top->addPart();
$stack->addPart();
$accum =& $stack->getAccum();
++$i;
}
elseif ( $found == 'equals' ) {
$findEquals = false; // shortcut for getFlags()
$partsCount = count( $stack->top->parts );
$stack->top->eqpos[$partsCount - 1] = strlen( $accum );
$stack->getCurrentPart()->eqpos = strlen( $accum );
$accum .= '=';
++$i;
}
@ -531,14 +558,10 @@ class Preprocessor_DOM implements Preprocessor {
# Output any remaining unclosed brackets
foreach ( $stack->stack as $piece ) {
if ( $piece->open == "\n" ) {
$stack->topAccum .= $piece->parts[0];
} else {
$stack->topAccum .= str_repeat( $piece->open, $piece->count ) . implode( '|', $piece->parts );
}
$stack->rootAccum .= $piece->breakSyntax();
}
$stack->topAccum .= '</root>';
$xml = $stack->topAccum;
$stack->rootAccum .= '</root>';
$xml = $stack->rootAccum;
wfProfileOut( __METHOD__.'-makexml' );
wfProfileIn( __METHOD__.'-loadXML' );
@ -561,6 +584,156 @@ class Preprocessor_DOM implements Preprocessor {
}
}
/**
* Stack class to help Preprocessor::preprocessToObj()
*/
class PPDStack {
var $stack, $rootAccum, $top;
var $out;
static $false = false;
function __construct() {
$this->stack = array();
$this->top = false;
$this->rootAccum = '';
$this->accum =& $this->rootAccum;
}
function count() {
return count( $this->stack );
}
function &getAccum() {
return $this->accum;
}
function getCurrentPart() {
if ( $this->top === false ) {
return false;
} else {
return $this->top->getCurrentPart();
}
}
function push( $data ) {
if ( $data instanceof PPDStackElement ) {
$this->stack[] = $data;
} else {
$this->stack[] = new PPDStackElement( $data );
}
$this->top = $this->stack[ count( $this->stack ) - 1 ];
$this->accum =& $this->top->getAccum();
}
function pop() {
if ( !count( $this->stack ) ) {
throw new MWException( __METHOD__.': no elements remaining' );
}
$temp = array_pop( $this->stack );
if ( count( $this->stack ) ) {
$this->top = $this->stack[ count( $this->stack ) - 1 ];
$this->accum =& $this->top->getAccum();
} else {
$this->top = self::$false;
$this->accum =& $this->rootAccum;
}
return $temp;
}
function addPart( $s = '' ) {
$this->top->addPart( $s );
$this->accum =& $this->top->getAccum();
}
function getFlags() {
if ( !count( $this->stack ) ) {
return array(
'findEquals' => false,
'findPipe' => false,
'inHeading' => false,
);
} else {
return $this->top->getFlags();
}
}
}
class PPDStackElement {
var $open, // Opening character (\n for heading)
$close, // Matching closing character
$count, // Number of opening characters found (number of "=" for heading)
$parts, // Array of PPDPart objects describing pipe-separated parts.
$lineStart; // True if the open char appeared at the start of the input line. Not set for headings.
function __construct( $data = array() ) {
$this->parts = array( new PPDPart );
foreach ( $data as $name => $value ) {
$this->$name = $value;
}
}
function &getAccum() {
return $this->parts[count($this->parts) - 1]->out;
}
function addPart( $s = '' ) {
$this->parts[] = new PPDPart( $s );
}
function getCurrentPart() {
return $this->parts[count($this->parts) - 1];
}
function getFlags() {
$partCount = count( $this->parts );
$findPipe = $this->open != "\n" && $this->open != '[';
return array(
'findPipe' => $findPipe,
'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
'inHeading' => $this->open == "\n",
);
}
/**
* Get the output string that would result if the close is not found.
*/
function breakSyntax( $openingCount = false ) {
if ( $this->open == "\n" ) {
$s = $this->parts[0]->out;
} else {
if ( $openingCount === false ) {
$openingCount = $this->count;
}
$s = str_repeat( $this->open, $openingCount );
$first = true;
foreach ( $this->parts as $part ) {
if ( $first ) {
$first = false;
} else {
$s .= '|';
}
$s .= $part->out;
}
}
return $s;
}
}
class PPDPart {
var $out; // Output accumulator string
// Optional member variables:
// eqpos Position of equals sign in output accumulator
// commentEnd Past-the-end input pointer for the last comment encountered
// visualEnd Past-the-end input pointer for the end of the accumulator minus comments
function __construct( $out = '' ) {
$this->out = $out;
}
}
/**
* An expansion frame, used as a context to expand the result of preprocessToDom()
*/
@ -1037,91 +1210,6 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
}
}
/**
* Stack class to help Parser::preprocessToDom()
*/
class PPDStack {
var $stack, $topAccum, $top;
function __construct() {
$this->stack = array();
$this->topAccum = '';
$this->top = false;
}
function &getAccum() {
if ( count( $this->stack ) ) {
return $this->top->getAccum();
} else {
return $this->topAccum;
}
}
function push( $data ) {
if ( $data instanceof PPDStackElement ) {
$this->stack[] = $data;
} else {
$this->stack[] = new PPDStackElement( $data );
}
$this->top =& $this->stack[ count( $this->stack ) - 1 ];
}
function pop() {
if ( !count( $this->stack ) ) {
throw new MWException( __METHOD__.': no elements remaining' );
}
$temp = array_pop( $this->stack );
if ( count( $this->stack ) ) {
$this->top =& $this->stack[ count( $this->stack ) - 1 ];
} else {
$this->top = false;
}
}
function getFlags() {
if ( !count( $this->stack ) ) {
return array(
'findEquals' => false,
'findPipe' => false,
'inHeading' => false,
);
} else {
return $this->top->getFlags();
}
}
}
class PPDStackElement {
var $open, $close, $count, $parts, $eqpos, $lineStart;
function __construct( $data = array() ) {
$this->parts = array( '' );
$this->eqpos = array();
foreach ( $data as $name => $value ) {
$this->$name = $value;
}
}
function &getAccum() {
return $this->parts[count($this->parts) - 1];
}
function addPart( $s = '' ) {
$this->parts[] = $s;
}
function getFlags() {
$partCount = count( $this->parts );
$findPipe = $this->open != "\n" && $this->open != '[';
return array(
'findPipe' => $findPipe,
'findEquals' => $findPipe && $partCount > 1 && !isset( $this->eqpos[$partCount - 1] ),
'inHeading' => $this->open == "\n",
);
}
}
class PPNode_DOM implements PPNode {
var $node;
@ -1143,7 +1231,7 @@ class PPNode_DOM implements PPNode {
$s .= $node->ownerDocument->saveXML( $node );
}
} else {
$s = $this->node->ownerDocument->saveXML( $node );
$s = $this->node->ownerDocument->saveXML( $this->node );
}
return $s;
}

View file

@ -6,7 +6,7 @@ $wgHooks['BeforeParserFetchTemplateAndtitle'][] = 'PPFuzzTester::templateHook';
class PPFuzzTester {
var $hairs = array(
'[[', ']]', '{{', '}}', '{{{', '}}}',
'[[', ']]', '{{', '{{', '}}', '}}', '{{{', '}}}',
'<', '>', '<nowiki', '<gallery', '</nowiki>', '</gallery>', '<nOwIkI>', '</NoWiKi>',
'<!--' , '-->',
"\n==", "==\n",
@ -23,6 +23,7 @@ class PPFuzzTester {
var $maxTemplates = 5;
//var $outputTypes = array( 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' );
var $entryPoints = array( 'testSrvus', 'testPst', 'testPreprocess' );
var $verbose = false;
static $currentTest = false;
function execute() {
@ -33,10 +34,14 @@ class PPFuzzTester {
echo "Unable to create 'results' directory\n";
exit( 1 );
}
for ( $i = 0; true; $i++ ) {
$overallStart = microtime( true );
$reportInterval = 1000;
for ( $i = 1; true; $i++ ) {
$t = -microtime( true );
try {
self::$currentTest = new PPFuzzTest( $this );
self::$currentTest->execute();
$passed = 'passed';
} catch ( MWException $e ) {
$testReport = self::$currentTest->getReport();
$exceptionReport = $e->getText();
@ -45,8 +50,30 @@ class PPFuzzTester {
file_put_contents( "results/ppft-$hash.fail",
"Input:\n$testReport\n\nException report:\n$exceptionReport\n" );
print "Test $hash failed\n";
$passed = 'failed';
}
if ( $i % 1000 == 0 ) {
$t += microtime( true );
if ( $this->verbose ) {
printf( "Test $passed in %.3f seconds\n", $t );
print self::$currentTest->getReport();
}
$reportMetric = ( microtime( true ) - $overallStart ) / $i * $reportInterval;
if ( $reportMetric > 25 ) {
if ( substr( $reportInterval, 0, 1 ) === '1' ) {
$reportInterval /= 2;
} else {
$reportInterval /= 5;
}
} elseif ( $reportMetric < 4 ) {
if ( substr( $reportInterval, 0, 1 ) === '1' ) {
$reportInterval *= 5;
} else {
$reportInterval *= 2;
}
}
if ( $i % $reportInterval == 0 ) {
print "$i tests done\n";
/*
$testReport = self::$currentTest->getReport();
@ -54,10 +81,14 @@ class PPFuzzTester {
file_put_contents( $filename, "Input:\n$testReport\n" );*/
}
}
wfLogProfilingData();
}
function makeInputText() {
$length = mt_rand( $this->minLength, $this->maxLength );
function makeInputText( $max = false ) {
if ( $max === false ) {
$max = $this->maxLength;
}
$length = mt_rand( $this->minLength, $max );
$s = '';
for ( $i = 0; $i < $length; $i++ ) {
$hairIndex = mt_rand( 0, count( $this->hairs ) - 1 );
@ -88,15 +119,16 @@ class PPFuzzTester {
}
class PPFuzzTest {
var $templates, $mainText, $title, $entryPoint;
var $templates, $mainText, $title, $entryPoint, $output;
function __construct( $tester ) {
global $wgMaxSigChars;
$this->parent = $tester;
$this->mainText = $tester->makeInputText();
$this->title = $tester->makeTitle();
//$this->outputType = $tester->pickOutputType();
$this->entryPoint = $tester->pickEntryPoint();
$this->nickname = $tester->makeInputText();
$this->nickname = $tester->makeInputText( $wgMaxSigChars + 10);
$this->fancySig = (bool)mt_rand( 0, 1 );
$this->templates = array();
}
@ -138,8 +170,9 @@ class PPFuzzTest {
$options = new ParserOptions;
$options->setTemplateCallback( array( $this, 'templateHook' ) );
//$wgParser->startExternalParse( $this->title, $options, constant( $this->outputType ) );
return call_user_func( array( $wgParser, $this->entryPoint ), $this->mainText, $this->title, $options );
$options->setTimestamp( wfTimestampNow() );
$this->output = call_user_func( array( $wgParser, $this->entryPoint ), $this->mainText, $this->title->getPrefixedText(), $options );
return $this->output;
}
function getReport() {
@ -156,6 +189,7 @@ class PPFuzzTest {
$s .= "[[$titleText]]: " . var_export( $template['text'], true ) . "\n";
}
}
$s .= "Output: " . var_export( $this->output, true ) . "\n";
return $s;
}
}
@ -163,6 +197,14 @@ class PPFuzzTest {
class PPFuzzUser extends User {
var $ppfz_test;
function load() {
if ( $this->mDataLoaded ) {
return;
}
$this->mDataLoaded = true;
$this->loadDefaults( $this->mName );
}
function getOption( $option, $defaultOverride = '' ) {
if ( $option === 'fancysig' ) {
return $this->ppfz_test->fancySig;
@ -182,10 +224,10 @@ if ( isset( $args[0] ) ) {
exit( 1 );
}
$test = unserialize( $testText );
print $test->getReport();
$result = $test->execute();
print "Test passed.\nResult: $result\n";
print "Test passed.\n";
} else {
$tester = new PPFuzzTester;
$tester->verbose = isset( $options['verbose'] );
$tester->execute();
}