Repository navigation
Avoid NRE when sorting by user-dependent keys without a user - #17395
Conversation
a768eee to
6167b65
Compare
Jellyfin API (v 12.0.0)ResultNo differences. Specifications are equivalent |
| { | ||
| if (user is null) | ||
| { | ||
| return Comparers.FirstOrDefault(c => c.Type == ItemSortBy.SortName); |
There was a problem hiding this comment.
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
6167b65 to
5d580ab
Compare
|
Good point — changed to |
| // treated as a user-dependent comparer: with no user it should still sort by date, not fall | ||
| // back to SortName. |
Bond-009
left a comment
There was a problem hiding this comment.
Not a fan of all the lengthy comments that easily get out of date, but functionality look OK
| // comparer were still tagged IUserBaseItemComparer, the null-user SortName fallback would | ||
| // return [Zulu, Mike, Alpha] and this assertion would fail. |
| // BaseItem.SortName/CreateSortName dereference this static; set it so SortName-fallback | ||
| // paths don't NRE in-process (mirrors AudioResolverTests in the sibling test project). |
|
@paoloantinori please reduce the comments |
|
Thanks @crobibero — removed the added comments in 8b70582. The production message and the test method names carry the intent on their own. |
|
crobibero
left a comment
There was a problem hiding this comment.
Please review our AI/LLM policies for future contributions.
https://jellyfin.org/docs/general/contributing/llm-policies/
I'll do, thanks! |
Changes
Same fix as #17394 (release-10.11.z), adapted for master. Fixes a
NullReferenceExceptionthat 500-ed any anonymous (API-key, nouserId)/Itemsrequest sorted by a user-dependent key (PlayCount,IsFavoriteOrLiked,DatePlayed,IsPlayed,IsUnplayed).LibraryManager.GetComparer: whenuseris null and the requested key requires a user (IUserBaseItemComparer), substitute theSortNamecomparer so the result stays deterministic instead of crashing. (GetCompareris identical to the release branch; the hunk applies line-for-line.)UserDataManager.GetUserData:ArgumentNullException.ThrowIfNull(user). On master this overload was rewritten to useResolveUserDataRow, so the NRE dereferencesuser.Idrather thanuser.InternalIdas on the release branch — same bug, different line; same guard.DateLastMediaAddedComparer: re-tagged fromIUserBaseItemComparertoIBaseItemComparer(itsGetDateisstaticand never readsUser), and dropped the unusedUser/UserManager/UserDataManagerproperties. Without this, the newSortNamefallback would wrongly engage forDateLastContentAddedon anonymous queries, returningSortNameorder 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 → nowArgumentNullException). Added to master's existingUserDataManagerTests(reuses its in-memory Sqlite fixture).LibraryManagerSortTests.Sort_UserDependentKey_NullUser_FallsBackToSortNameWithoutThrowing—Sortwith a user-dependent key + null user no longer throws and returns items ordered by theSortNamefallback (direction preserved).LibraryManagerSortTests.Sort_DateLastContentAdded_NullUser_OrdersByDateNotSortName— guards thatDateLastContentAddedstill sorts by date with no user (fixture chosen so date-desc and SortName-desc disagree, so a revert is caught).Full
Jellyfin.Server.Implementations.Testssuite: 642 passed, 0 failed.Issues
Fixes #17393
Discovered while working on jellyfin-alexa-plugin; the plugin-side workaround is paoloantinori/jellyfin-alexa-plugin@25e5446.