Page MenuHomePhabricator

Some history views and diffs unavailable on zh.wikipedia.org (Fatal ParameterTypeException: Bad value for parameter $fragment)
Closed, ResolvedPublicPRODUCTION ERROR

Description

Examples:

https://zh.wikipedia.org/w/index.php?title=%E8%90%A8%E5%A1%9E%E5%85%8B%E6%96%AF%E5%85%AC%E7%88%B5%E5%A4%AB%E4%BA%BA%E6%A2%85%E6%A0%B9&diff=54343065&oldid=54339003

Error: [XNO8wQpAIC0AAFgBaJMAAAAX] 2019-05-09 05:38:09: 类型“Wikimedia\Assert\ParameterTypeException”的致命例外

https://zh.wikipedia.org/w/index.php?title=%E7%A7%8B%E5%85%83%E5%BA%B7&curid=1069252&diff=54302740&oldid=54064765

Error: [XNO8IwpAICEAAE@gG9QAAAAY] 2019-05-09 05:35:31: 类型“Wikimedia\Assert\ParameterTypeException”的致命例外


message
Bad value for parameter $fragment: must be a string
trace
#0 /srv/mediawiki/php-1.34.0-wmf.3/includes/Linker.php(1236): TitleValue->__construct(integer, string, boolean)
#1 /srv/mediawiki/php-1.34.0-wmf.3/includes/Linker.php(1264): Closure$Linker::formatAutocomments(array)
#2 /srv/mediawiki/php-1.34.0-wmf.3/includes/Linker.php(1165): Linker::formatAutocomments(string, Title, boolean, NULL)
#3 /srv/mediawiki/php-1.34.0-wmf.3/includes/Linker.php(1531): Linker::formatComment(string, Title, boolean, NULL)
#4 /srv/mediawiki/php-1.34.0-wmf.3/includes/Linker.php(1562): Linker::commentBlock(string, Title, boolean, NULL, boolean)
#5 /srv/mediawiki/php-1.34.0-wmf.3/includes/diff/DifferenceEngine.php(593): Linker::revComment(Revision, boolean, boolean)
#6 /srv/mediawiki/php-1.34.0-wmf.3/includes/page/Article.php(931): DifferenceEngine->showDiffPage(boolean)
#7 /srv/mediawiki/php-1.34.0-wmf.3/includes/page/Article.php(615): Article->showDiffPage()
#8 /srv/mediawiki/php-1.34.0-wmf.3/includes/actions/ViewAction.php(68): Article->view()
#9 /srv/mediawiki/php-1.34.0-wmf.3/includes/MediaWiki.php(499): ViewAction->show()
#10 /srv/mediawiki/php-1.34.0-wmf.3/includes/MediaWiki.php(294): MediaWiki->performAction(Article, Title)
#11 /srv/mediawiki/php-1.34.0-wmf.3/includes/MediaWiki.php(865): MediaWiki->performRequest()
#12 /srv/mediawiki/php-1.34.0-wmf.3/includes/MediaWiki.php(515): MediaWiki->main()
#13 /srv/mediawiki/php-1.34.0-wmf.3/index.php(42): MediaWiki->run()
#14 /srv/mediawiki/w/index.php(3): include(string)
#15 {main}

Event Timeline

page rev 54339003 Internal error: https://zh.wikipedia.org/w/index.php?title=%E8%90%A8%E5%A1%9E%E5%85%8B%E6%96%AF%E5%85%AC%E7%88%B5%E5%A4%AB%E4%BA%BA%E6%A2%85%E6%A0%B9&oldid=54339003

[XNPHRQpAICwAAIJU420AAABS] 2019-05-09 06:23:01: 类型“Wikimedia\Assert\ParameterAssertionException”的致命例外

Daimona renamed this task from Internal error (ParameterTypeException) when diff certain history on zh.wiki to ParameterTypeException when viewing diffs: Bad value for parameter $fragment: must be a string .May 9 2019, 8:01 AM
Daimona updated the task description. (Show Details)
Daimona triaged this task as High priority.May 9 2019, 8:03 AM
Daimona subscribed.

Seen 400 times in the last 24 hours, can't tell what the impact is though.

For my debug, when a revision comment contains /**/ (patten: /\*\s*\*/), the exception will be throw. E.g. my test edit in sandbox

Linker::formatAutocomments() did not handle empty autocomment.

Change 509028 had a related patch set uploaded (by 星耀晨曦; owner: 星耀晨曦):
[mediawiki/core@master] [DO NOT MERGE] Using debug T222857

https://gerrit.wikimedia.org/r/509028

在T222857#5169523中,@RazeSoldier写道:

For my debug, when a revision comment contains /**/ (patten: /\*\s*\*/), the exception will be throw. E.g. my test edit in sandbox

I add a AF on zhwiki: https://zh.wikipedia.org/wiki/Special:%E6%BB%A5%E7%94%A8%E8%BF%87%E6%BB%A4%E5%99%A8/258

The stack trace:

ParameterTypeException: Bad value for parameter $fragment: must be a string

#0 /srv/mediawiki/php-1.34.0-wmf.5/includes/Linker.php(1236): TitleValue->__construct(integer, string, boolean)
#1 /srv/mediawiki/php-1.34.0-wmf.5/includes/Linker.php(1264): Closure$Linker::formatAutocomments(array)
#2 /srv/mediawiki/php-1.34.0-wmf.5/includes/Linker.php(1165): Linker::formatAutocomments(string, Title, boolean, NULL)

The code:

						$section = substr( Parser::guessSectionNameFromStrippedText( $section ), 1 );
						if ( $local ) {
							$sectionTitle = new TitleValue( NS_MAIN, '', $section );

This means $section is a boolean. The only boolean substr() can return false.substr() is well-known for tolerating shorter strings. For example, using the second parameter:

$x = 'yoman';
substr( $x, 0, 4 ); "yoma"
substr( $x, 0, 5 ); "yoman"
substr( $x, 0, 6 ); "yoman" -- same, no error. It means "at most", but shorter is okay.

$x = 'yoman';
substr( $x, 0, 1 ); "y" -- get first character

$x = '';
substr( $x, 0, 1 ); "" -- or, empty string if there is no first character

The second parameter (length limit) does not cause an error, and does not cause boolean false. It is always returns a safe string.

So, where does bool(false) come from? The substr() function returns false, if the string is not long enough for the specified starting point. The first parameter (start point) is different from the second parameter (length limit).

$x = 'yo';
substr( $x, 0 ); // "yo"
substr( $x, 1 ); // "o"
substr( $x, 2 ); // bool(false)


$x = '';
substr( $x, 1 ); // bool(false)

That is why the code is failing. We must check for this separately.

Krinkle renamed this task from ParameterTypeException when viewing diffs: Bad value for parameter $fragment: must be a string to Some article history and diff unavailable on zh.wikipedia.org (Fatal ParameterTypeException: Bad value for parameter $fragment).May 19 2019, 10:36 AM
Krinkle renamed this task from Some article history and diff unavailable on zh.wikipedia.org (Fatal ParameterTypeException: Bad value for parameter $fragment) to Some history views and diffs unavailable on zh.wikipedia.org (Fatal ParameterTypeException: Bad value for parameter $fragment).May 19 2019, 10:47 AM

The example revision is revision 54064765 of zh.wikipedia.org.

It fatals when trying to view it on its own: https://zh.wikipedia.org/w/index.php?oldid=54064765.

It also fatals when trying to diff it with the revision before or after it:

Interestingly, it does not fatal when viewing the history page:

Eventhough, it does try to render it in some way here as well. So once we figure out how to fix this, we should also figure how how it is that the history page is more tolerant towards this, and we should consider using that mechanism more widely in the future.

export.png (642×1 px, 382 KB)

Using the API to get the original input:

{
    "timestamp": "2019-04-18T06:52:35Z",
    "comment": "/*  */ // Edit via Wikiplus"
},

I'll use this as test case.

Something worth noting about the behaviour of substr in edge-cases: it's different in PHP5 (and HHVM) and PHP7. I came across this peculiarity while writing this patch for AF. See the changelog on php.net.
This basically means that in PHP7 you have:

substr( 'yo', 2 ); // ""

There might be similar instances of the same problem throughout the whole codebase, which could potentially cause issues after the switch to PHP7; although in this case, PHP7 would fix (and not cause) an issue appearing only on HHVM.


EDIT Also note that what I said above is only true if the second parametr to substr is equal to the length of the string passed in (first parameter). And for what concerns HHVM, it obviously depend on the version; see 3v4l.

If I just leave /* */ comment, Bad value for parameter $dbkey : should not be empty will occur. Already reproduce in the test patch.

$x = 'yo';
substr( $x, 0 ); // "yo"
substr( $x, 1 ); // "o"
substr( $x, 2 ); // bool(false)


$x = '';
substr( $x, 1 ); // bool(false)

That is why the code is failing. We must check for this separately.

Okay, so while the above is true. It isn't relevant to this issue.

I thought the problem is that $section is empty string, and we try to remove the first character of nothing.

The real issue is more subtle.

				$section = substr( Parser::guessSectionNameFromStrippedText( $section ), 1 );
				if ( $local ) {
					$sectionTitle = new TitleValue( NS_MAIN, '', $section );

In the Linker for the zhwiki revision, $section is an empty string, but the guessSectionNameFromStrippedText() function changes that to #. So the string is not empty.

The real case is, as such:

$x = 'y';
substr( $x, 1 );

This is valid on PHP 7 and returns the empty string. But, on PHP 5 and the HHVM version we use, this returns bool(false).

So, in summary:

  • Using substr() with a zero start point, you can use any max length limit and PHP will return as many as there are, sometimes zero, and always safely a string.
  • Using substr() with a non-zero start point, it always returns false if this start point is beyond the end of the string.
  • Using substr() with a non-zero start point that is equal to to the length of the string (not beyond), then PHP 7 will return empty string, but PHP 5/HHVM rejects and returns false.
var_dump( substr( '', 0, 2 ) ); // ""

var_dump( substr( 'a', 3 ) ); // bool(false)

var_dump( substr( 'a', 1 ) ); // "" on PHP 7+, bool(false) on PHP5/HHVM

It seems that this problem is essentially the same as the problem with T222628. A different error message will appear because of the difference(HHVM/PHP7) in PHP interpreter.

Change 511192 had a related patch set uploaded (by Krinkle; owner: Krinkle):
[mediawiki/core@master] [WIP] Linker: Fix fatal

https://gerrit.wikimedia.org/r/511192

Change 511192 merged by jenkins-bot:
[mediawiki/core@master] Linker: Fix fatal error for "/* */" in an edit summary

https://gerrit.wikimedia.org/r/511192

@Krinkle Still have a problem. See the beta site.

$section; // Edge-case: false(HHVM) / ''(PHP7)
if ($section === false) {
    $section = ''; // Will run under HHVM
}
if ($local) {
    $title = new TitleValue(NS_MAIN, '', $section);
} else {
...

This means that in the case of edges, $section is always an empty string. But in TitleValue constructor there are parameters will be check.

Assert::parameter( $dbkey !== '' || ( $fragment !== '' && $namespace === NS_MAIN ),
    '$dbkey', 'should not be empty unless namespace is main and fragment is non-empty' );

That's a different error (two lines further, so we're making progress...), that error relates to the weird and unexpected use of '' in this constructor which T222628 covers already. The solution there is not with the input to the class, but with the use of the class at all.

The solution for this task, was to fix the input.

The solution for T222628, will likely be to not involve the class at all for these specific situations. That will be a more complicated change and require looking at the original behaviour prior to b6e1e99bec8.

Change 511462 had a related patch set uploaded (by Krinkle; owner: Krinkle):
[mediawiki/core@wmf/1.34.0-wmf.5] Linker: Fix fatal error for "/* */" in an edit summary

https://gerrit.wikimedia.org/r/511462

Change 511462 merged by jenkins-bot:
[mediawiki/core@wmf/1.34.0-wmf.5] Linker: Fix fatal error for "/* */" in an edit summary

https://gerrit.wikimedia.org/r/511462

Krinkle claimed this task.

Confirmed the specific exception no longer happens after the above patch. Instead it now fails one line further down, which is tracked at T222628.

Mentioned in SAL (#wikimedia-operations) [2019-05-20T18:55:00Z] <krinkle@deploy1001> Synchronized php-1.34.0-wmf.5/includes/Linker.php: T222857 / Iecc2140fabd3 (duration: 00m 54s)

Change 509028 abandoned by 星耀晨曦:
[DO NOT MERGE] Using debug T222857

Reason:
debug end

https://gerrit.wikimedia.org/r/509028

mmodell changed the subtype of this task from "Task" to "Production Error".Aug 28 2019, 11:07 PM