Page MenuHomePhabricator

Fatal exception of type "Wikimedia\Assert\PreconditionException" when marking a page for translation
Closed, ResolvedPublic2 Estimated Story PointsBUG REPORT

Description

Steps to replicate the issue (include links if applicable):

  • Create a translatable page
  • Mark the page for translation
  • Make modifications to a translation unit in the translatable page
  • Try to mark the page for translation again

What happens?:
The following error is encountered:

[2024-12-11T13:34:30.710156+00:00] exception.ERROR: [9f11d9df2a955b242e9dbe9f] /w/i.php?title=Special:PageTranslation&target=Group+descriptions&do=mark   Wikimedia\Assert\PreconditionException: Precondition failed: This Title instance does not represent a proper page, but merely a link target. {"exception":"[object] (Wikimedia\\Assert\\PreconditionException(code: 0): Precondition failed: This Title instance does not represent a proper page, but merely a link target. at /srv/mediawiki/tags/2024-12-11_13:29:32/vendor/wikimedia/assert/src/Assert.php:49)
[stacktrace]
#0 /srv/mediawiki/tags/2024-12-11_13:29:32/includes/title/Title.php(3816): Wikimedia\\Assert\\Assert::precondition()
#1 /srv/mediawiki/tags/2024-12-11_13:29:32/includes/title/Title.php(3837): MediaWiki\\Title\\Title->assertProperPage()
#2 /srv/mediawiki/tags/2024-12-11_13:29:32/includes/Revision/RevisionRecord.php(112): MediaWiki\\Title\\Title->toPageIdentity()
#3 /srv/mediawiki/tags/2024-12-11_13:29:32/includes/Revision/MutableRevisionRecord.php(113): MediaWiki\\Revision\\RevisionRecord->__construct()
#4 /srv/mediawiki/tags/2024-12-11_13:29:32/extensions/Translate/src/PageTranslation/PageTranslationSpecialPage.php(894): MediaWiki\\Revision\\MutableRevisionRecord->__construct()
#5 /srv/mediawiki/tags/2024-12-11_13:29:32/extensions/Translate/src/PageTranslation/PageTranslationSpecialPage.php(355): MediaWiki\\Extension\\Translate\\PageTranslation\\PageTranslationSpecialPage->showPage()
#6 /srv/mediawiki/tags/2024-12-11_13:29:32/extensions/Translate/src/PageTranslation/PageTranslationSpecialPage.php(190): MediaWiki\\Extension\\Translate\\PageTranslation\\PageTranslationSpecialPage->onActionMark()
....

What should have happened instead?:
The diff for the page / translation unit should be displayed so that the user can mark the page for translation.

Software version (on Special:Version page; skip for WMF-hosted wikis like Wikipedia):
MediaWiki: d1d978056e6846610623fc3a36bf2b21f1f3adea
Translate: master

Other information (browser name/version, screenshots, etc.):
Appears to be caused due to changes made in 1094003: Revision: make RevisionRecord::getPage() return a value object | https://gerrit.wikimedia.org/r/c/mediawiki/core/+/1094003

For generating a diff, we are using the following:

$oldContent = ContentHandler::makeContent( $oldTextForDiff, $diff->getTitle() );
$oldRevision = new MutableRevisionRecord( $diff->getTitle() );
$oldRevision->setContent( SlotRecord::MAIN, $oldContent );

$newContent = ContentHandler::makeContent( $value, $diff->getTitle() );
$newRevision = new MutableRevisionRecord( $diff->getTitle() );
$newRevision->setContent( SlotRecord::MAIN, $newContent );

$diff->setRevisions( $oldRevision, $newRevision );
$text = $diff->getDiff( '', '' );

This fails because:

  1. $diff->getTitle() returns Special:PageTranslation
  2. The code $page = $page->toPageIdentity(); added in RevisionRecord::__construct(See: 1e7a520f1f42770f7eaeb4369805b427efa32634) calls Title::assertProperPage
  3. Title::assertProperPage then calls Title::canExist which returns false for a SpecialPage.

Event Timeline

abi_ triaged this task as High priority.Dec 11 2024, 2:33 PM

Change #1102316 had a related patch set uploaded (by Abijeet Patro; author: Abijeet Patro):

[mediawiki/extensions/Translate@master] Use a non special page as title when displaying diff

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

Change #1102316 merged by jenkins-bot:

[mediawiki/extensions/Translate@master] Use a non specialpage as title when creating MutableRevisionRecord

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

Appearing in other code paths too:

/w/api.php   Wikimedia\Assert\PreconditionException: Precondition failed: This Title instance does not represent a proper page, but merely a link target.
[stacktrace]
#0 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/title/Title.php(3816): Wikimedia\\Assert\\Assert::precondition()
#1 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/title/Title.php(3837): MediaWiki\\Title\\Title->assertProperPage()
#2 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/Revision/RevisionRecord.php(112): MediaWiki\\Title\\Title->toPageIdentity()
#3 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/Revision/MutableRevisionRecord.php(113): MediaWiki\\Revision\\RevisionRecord->__construct()
#4 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/parser/Parsoid/LintErrorChecker.php(79): MediaWiki\\Revision\\MutableRevisionRecord->__construct()
#5 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/preferences/SignatureValidator.php(268): MediaWiki\\Parser\\Parsoid\\LintErrorChecker->checkSome()
#6 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/preferences/SignatureValidator.php(124): MediaWiki\\Preferences\\SignatureValidator->checkLintErrors()
#7 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/preferences/DefaultPreferencesFactory.php(722): MediaWiki\\Preferences\\SignatureValidator->validateSignature()
#8 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/preferences/DefaultPreferencesFactory.php(256): MediaWiki\\Preferences\\DefaultPreferencesFactory->profilePreferences()
#9 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/preferences/DefaultPreferencesFactory.php(2111): MediaWiki\\Preferences\\DefaultPreferencesFactory->getFormDescriptor()
#10 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/api/ApiOptionsBase.php(118): MediaWiki\\Preferences\\DefaultPreferencesFactory->getResetKinds()
#11 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/api/ApiMain.php(1975): MediaWiki\\Api\\ApiOptionsBase->execute()
#12 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/api/ApiMain.php(943): MediaWiki\\Api\\ApiMain->executeAction()
#13 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/api/ApiMain.php(914): MediaWiki\\Api\\ApiMain->executeActionWithErrorHandling()
#14 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/api/ApiEntryPoint.php(153): MediaWiki\\Api\\ApiMain->execute()
#15 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/MediaWikiEntryPoint.php(201): MediaWiki\\Api\\ApiEntryPoint->execute()
#16 /srv/mediawiki/tags/2024-12-11_17:21:09/api.php(44): MediaWiki\\MediaWikiEntryPoint->run()
#17 {main}
Krinkle subscribed.

Tagging Parsoid/Linter per the latest stack trace.

If we end up reverting/changing core to allow this (like it used to), then you can un-tag those again. If we end up chasing down the violations and requiring them to accomodate each diff with a "can-exist" page title, then the next place to fix would be Parsoid/Linter.

Given the end-of-year timeline and this week being the last train of the year, it may be prudent to revert and chase these down in the new year, to avoid unnecessary breakage. I am off most of the day today.

Change #1104736 had a related patch set uploaded (by Subramanya Sastry; author: Subramanya Sastry):

[mediawiki/core@master] Revert "Revision: make RevisionRecord::getPage() return a value object"

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

Appearing in other code paths too:

/w/api.php   Wikimedia\Assert\PreconditionException: Precondition failed: This Title instance does not represent a proper page, but merely a link target.
[stacktrace]
#0 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/title/Title.php(3816): Wikimedia\\Assert\\Assert::precondition()
#1 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/title/Title.php(3837): MediaWiki\\Title\\Title->assertProperPage()
#2 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/Revision/RevisionRecord.php(112): MediaWiki\\Title\\Title->toPageIdentity()
#3 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/Revision/MutableRevisionRecord.php(113): MediaWiki\\Revision\\RevisionRecord->__construct()
#4 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/parser/Parsoid/LintErrorChecker.php(79): MediaWiki\\Revision\\MutableRevisionRecord->__construct()
#5 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/preferences/SignatureValidator.php(268): MediaWiki\\Parser\\Parsoid\\LintErrorChecker->checkSome()
#6 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/preferences/SignatureValidator.php(124): MediaWiki\\Preferences\\SignatureValidator->checkLintErrors()
#7 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/preferences/DefaultPreferencesFactory.php(722): MediaWiki\\Preferences\\SignatureValidator->validateSignature()
#8 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/preferences/DefaultPreferencesFactory.php(256): MediaWiki\\Preferences\\DefaultPreferencesFactory->profilePreferences()
#9 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/preferences/DefaultPreferencesFactory.php(2111): MediaWiki\\Preferences\\DefaultPreferencesFactory->getFormDescriptor()
#10 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/api/ApiOptionsBase.php(118): MediaWiki\\Preferences\\DefaultPreferencesFactory->getResetKinds()
#11 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/api/ApiMain.php(1975): MediaWiki\\Api\\ApiOptionsBase->execute()
#12 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/api/ApiMain.php(943): MediaWiki\\Api\\ApiMain->executeAction()
#13 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/api/ApiMain.php(914): MediaWiki\\Api\\ApiMain->executeActionWithErrorHandling()
#14 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/api/ApiEntryPoint.php(153): MediaWiki\\Api\\ApiMain->execute()
#15 /srv/mediawiki/tags/2024-12-11_17:21:09/includes/MediaWikiEntryPoint.php(201): MediaWiki\\Api\\ApiEntryPoint->execute()
#16 /srv/mediawiki/tags/2024-12-11_17:21:09/api.php(44): MediaWiki\\MediaWikiEntryPoint->run()
#17 {main}

What's going on here is that someone is trying to change their signature. The SignatureValidator uses Parsoid+Linter to check that the new signature is valid (doesn't have lint errors). We construct a MutableRevisionRecord in the process, and end up crashing when that MutableRevisionRecord is expected to represent a PageIdentity aka "an editable page". But of course we're parsing a user signature, we don't have an "editable page"! What "context page title" are we supposed to set here?

This is a case of T278376: Parser Use of RevisionRecord for a page that can't exist: (eg Special:MyLanguage/Main Page), and I think the assumption that the MutableRevisionRecord must represent a PageIdentity (that is, "an editable page") instead of a PageReference ("a viewable page") is at fault here. The parser is invoked many times where the context is "a viewable page" not "an editable page", and it uses MutableRevisionRecord as a pair type to couple a page title with a revision id (always 0 in the case of a non-editable page, but often 0 when the page is editable but not created etc as well).

This is a fundamental mismatch in page modeling (https://www.mediawiki.org/wiki/Manual:Modeling_pages).

Tagging Parsoid/Linter per the latest stack trace.

If we end up reverting/changing core to allow this (like it used to), then you can un-tag those again. If we end up chasing down the violations and requiring them to accomodate each diff with a "can-exist" page title, then the next place to fix would be Parsoid/Linter.

It's not checking "can exist" (that's PageReference) it is checking "can edit" (PageIdentity) and that is fundamentally incompatible with the way the parser is used (eg on Special pages).

Change #1104736 merged by jenkins-bot:

[mediawiki/core@master] Revert "Revision: make RevisionRecord::getPage() return a value object"

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

@abi_ on what wiki did this happen? Or more importantly, with what version of MediaWiki? Is this from production? Or from CI?

Looks like it was on translatewiki.net, the version was presumably master as of 2024-12-11 13:29:32.

Both the revert and the patch made it to MW-1.44-notes (1.44.0-wmf.8; 2024-12-17) and the breaking change was only in that branch. That thus never made it to Wikimedia production. That was apparently caught on translatewiki (which runs out of the master branch), was fixed in time and thus is no more blocking the train ;)

Resolved by the revert

This likely needs a follow-up discussion. I suggest to have it on another place/task for clarity.

Both the revert and the patch made it to MW-1.44-notes (1.44.0-wmf.8; 2024-12-17) and the breaking change was only in that branch. That thus never made it to Wikimedia production. That was apparently caught on translatewiki (which runs out of the master branch), was fixed in time and thus is no more blocking the train ;)

Ah, I see. I was very confused by the patch breaking this but not fixing T380677. This explains it!

Fun fact: the issue with SignatureValidator wasn't caught in CI because it only happens if the main page is a special page. SignatureValidator uses the main page pas parsing context, which usually works fine... but on translatewiki.net it doesn't.

Fun fact: the issue with SignatureValidator wasn't caught in CI because it only happens if the main page is a special page. SignatureValidator uses the main page pas parsing context, which usually works fine... but on translatewiki.net it doesn't.

Using the main page as a placeholder title should be declared as an anti pattern?

Using the main page as a placeholder title should be declared as an anti pattern?

It's not grate, but it's IMHO better than using a special page. It's a bit annoying that a page is required as context for parsing at all. It should be optional.

When validating a signature, we need *some* page as context. The user's talk page would be a logical choice, but then you'd have to supply a user to the vlidator.

Fun fact: the issue with SignatureValidator wasn't caught in CI because it only happens if the main page is a special page. SignatureValidator uses the main page pas parsing context, which usually works fine... but on translatewiki.net it doesn't.

Hmm, I can't find where SignatureValidator does it. It seems to instead hardcode SpecialPage::getTitleFor( 'Preferences' ) in several places as the context title.

Using the main page as a placeholder title should be declared as an anti pattern?

I agree that it's an anti-pattern. Other code sometimes special-cases the main page in interesting ways. For example, MobileFrontend used to apply special transformations to it (T32405).

If you use a random page name in the main namespace, you will also have trouble on wikis where the main namespace is not wikitext, like Wikidata. (I just noticed that this was the workaround in https://gerrit.wikimedia.org/r/c/mediawiki/extensions/Translate/+/1102316/5/src/Synchronization/MessageWebImporter.php, that might be bad.)

When validating a signature, we need *some* page as context. The user's talk page would be a logical choice, but then you'd have to supply a user to the vlidator.

The user’s talk page can also be problematic, as links to the user talk page would turn into self-links, which could make checkUserLinks() fail.

Fun fact: the issue with SignatureValidator wasn't caught in CI because it only happens if the main page is a special page. SignatureValidator uses the main page pas parsing context, which usually works fine... but on translatewiki.net it doesn't.

Hmm, I can't find where SignatureValidator does it. It seems to instead hardcode SpecialPage::getTitleFor( 'Preferences' ) in several places as the context title.

rMW includes/parser/Parsoid/LintErrorChecker.php uses $this->titleFactory->newMainPage().