Skip to content

Avoid NRE when sorting by user-dependent keys without a user - #17395

Merged
crobibero merged 2 commits into
jellyfin:masterfrom
paoloantinori:fix/userdata-null-user-nre-master
Jul 25, 2026
Merged

crobibero merged 2 commits into
jellyfin:masterfrom
paoloantinori:fix/userdata-null-user-nre-master

Conversation

@paoloantinori

@paoloantinori paoloantinori commented Jul 21, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Same fix as #17394 (release-10.11.z), adapted for master. Fixes a NullReferenceException that 500-ed any anonymous (API-key, no userId) /Items request sorted by a user-dependent key (PlayCount, IsFavoriteOrLiked, DatePlayed, IsPlayed, IsUnplayed).

  • LibraryManager.GetComparer: when user is null and the requested key requires a user (IUserBaseItemComparer), substitute the SortName comparer so the result stays deterministic instead of crashing. (GetComparer is identical to the release branch; the hunk applies line-for-line.)
  • UserDataManager.GetUserData: ArgumentNullException.ThrowIfNull(user). On master this overload was rewritten to use ResolveUserDataRow, so the NRE dereferences user.Id rather than user.InternalId as on the release branch — same bug, different line; same guard.
  • DateLastMediaAddedComparer: re-tagged from IUserBaseItemComparer to IBaseItemComparer (its GetDate is static and never reads User), and dropped the unused User/UserManager/UserDataManager properties. Without this, the new SortName fallback would wrongly engage for DateLastContentAdded on anonymous queries, returning SortName order instead of date order.

The crash surfaced as System.InvalidOperationException: Failed to compare two elements in the array (wrapping the NRE from the LINQ sort).

Code assistance

None — hand-written null-guard fix.

Tests

  • UserDataManagerTests.GetUserData_NullUser_ThrowsArgumentNullException — reproduces the crash (NRE → now ArgumentNullException). Added to master's existing UserDataManagerTests (reuses its in-memory Sqlite fixture).
  • LibraryManagerSortTests.Sort_UserDependentKey_NullUser_FallsBackToSortNameWithoutThrowing — Sort with a user-dependent key + null user no longer throws and returns items ordered by the SortName fallback (direction preserved).
  • LibraryManagerSortTests.Sort_DateLastContentAdded_NullUser_OrdersByDateNotSortName — guards that DateLastContentAdded still sorts by date with no user (fixture chosen so date-desc and SortName-desc disagree, so a revert is caught).

Full Jellyfin.Server.Implementations.Tests suite: 642 passed, 0 failed.

Issues

Fixes #17393


Discovered while working on jellyfin-alexa-plugin; the plugin-side workaround is paoloantinori/jellyfin-alexa-plugin@25e5446.

@paoloantinori
paoloantinori force-pushed the fix/userdata-null-user-nre-master branch from a768eee to 6167b65 Compare July 21, 2026 08:56
@jellyfin-bot

Copy link
Copy Markdown
Contributor

Jellyfin API (v 12.0.0)


Result


No differences. Specifications are equivalent

{
if (user is null)
{
return Comparers.FirstOrDefault(c => c.Type == ItemSortBy.SortName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should throw an ArgumentException instead IMO

A query sorted by a user-dependent key (PlayCount, IsFavoriteOrLiked,
DatePlayed, IsPlayed, IsUnplayed) but carrying no User caused a
NullReferenceException inside UserDataManager.GetUserData, surfacing as
"Failed to compare two elements in the array" (InvalidOperationException
wrapping the NRE from the LINQ sort) and 500-ing the /Items request.

Root cause: LibraryManager.GetComparer assigned comparer.User = user
without a null guard, so PlayCountComparer.GetValue called
UserDataManager.GetUserData(null, item), dereferencing user.Id.

Two-part fix:
- LibraryManager.GetComparer: when user is null and the sort key requires a
  user (IUserBaseItemComparer), substitute the SortName comparer so the
  result stays deterministic instead of 500-ing. SortName is the project's
  canonical tiebreaker (ItemsController injects it for album-by-artist).
- UserDataManager.GetUserData: ArgumentNullException.ThrowIfNull(user) as
  defense in depth (matches the existing guards on the SaveUserData
  overloads in the same file). On master this overload was rewritten to use
  ResolveUserDataRow, so the NRE dereferences user.Id rather than
  user.InternalId as on the release branch — same bug, different line.

Also fixes DateLastMediaAddedComparer being statically mis-tagged as
IUserBaseItemComparer: its GetDate is static and never reads User, so it
does not need one. Without this, the SortName fallback above would wrongly
engage for DateLastContentAdded on anonymous queries (returning SortName
order instead of date order). Re-tagged to IBaseItemComparer and dropped the
unused User/UserManager/UserDataManager properties.

Tests:
- UserDataManagerTests.GetUserData_NullUser_ThrowsArgumentNullException:
  reproduces the crash (NRE -> now ArgumentNullException). Added to master's
  existing UserDataManagerTests.
- LibraryManagerSortTests.Sort_UserDependentKey_NullUser_FallsBackToSortNameWithoutThrowing:
  Sort with a user-dependent key + null user no longer throws and returns
  items ordered by the SortName fallback (direction preserved).
- LibraryManagerSortTests.Sort_DateLastContentAdded_NullUser_OrdersByDateNotSortName:
  guards that DateLastContentAdded still sorts by date with no user (fixture
  chosen so date-desc and SortName-desc disagree, so a revert is caught).

Full Jellyfin.Server.Implementations.Tests suite: 642 passed, 0 failed.

Fixes jellyfin#17393
@paoloantinori
paoloantinori force-pushed the fix/userdata-null-user-nre-master branch from 6167b65 to 5d580ab Compare July 22, 2026 05:44
@paoloantinori

Copy link
Copy Markdown
Contributor Author

Good point — changed to throw new ArgumentException per your feedback. The caller now gets a clear error instead of a silent substitution. Updated both this PR and #17394 (release-10.11.z) for consistency.

Comment on lines +51 to +52
// treated as a user-dependent comparer: with no user it should still sort by date, not fall
// back to SortName.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outdated comment

@Bond-009 Bond-009 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of all the lengthy comments that easily get out of date, but functionality look OK

Comment on lines +58 to +59
// comparer were still tagged IUserBaseItemComparer, the null-user SortName fallback would
// return [Zulu, Mike, Alpha] and this assertion would fail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Comment on lines +85 to +86
// BaseItem.SortName/CreateSortName dereference this static; set it so SortName-fallback
// paths don't NRE in-process (mirrors AudioResolverTests in the sibling test project).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

@theguymadmax theguymadmax moved this from Todo to Approved in Jellyfin v12 Jul 23, 2026
@crobibero

Copy link
Copy Markdown
Member

@paoloantinori please reduce the comments

@paoloantinori

Copy link
Copy Markdown
Contributor Author

Thanks @crobibero — removed the added comments in 8b70582. The production message and the test method names carry the intent on their own.

@sonarqubecloud

Copy link
Copy Markdown

@crobibero crobibero left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review our AI/LLM policies for future contributions.
https://jellyfin.org/docs/general/contributing/llm-policies/

@crobibero
crobibero merged commit ebb66f6 into jellyfin:master Jul 25, 2026
5 checks passed
@github-project-automation github-project-automation Bot moved this from Approved to Done in Jellyfin v12 Jul 25, 2026
@paoloantinori

Copy link
Copy Markdown
Contributor Author

Please review our AI/LLM policies for future contributions.
https://jellyfin.org/docs/general/contributing/llm-policies/

I'll do, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

NullReferenceException in UserDataManager.GetUserData when sorting by user-dependent keys without a user

6 participants