Skip to content

fix(libSceIcu): follow ICU for null escape inputs and negative u_unescape capacities - #12451

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
simnJS:fix/icu-unescape-invalid-input
Oct 29, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
simnJS:fix/icu-unescape-invalid-input

Conversation

@simnJS

@simnJS simnJS commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

What

u_unescapeAt and u_unescape follow ICU 78.1's ustring.cpp, but throw NotImplemented_nid_no_patch for null pointers and for a negative capacity with a destination, so tools/progress.py counts both as stubs. ICU answers or faults in each of these cases:

  • u_unescapeAt reads *offset first and calls charAt once the offset is in range, checking neither. A null offset, or a null callback with an in-range offset, now throws std::invalid_argument (APS5_INVALID_ARG_EX), as fix(libSceLibreSsl): reject null strings in the ASN.1 accessors as invalid arguments #11111 did where LibreSSL faults. An out-of-range offset still returns -1 without calling the callback.
  • u_unescape reads *src without a check, so a null source now throws std::invalid_argument.
  • For a negative capacity with a destination, _appendUChars clamps the capacity to 0, and U16_LENGTH(c32) <= (destCapacity - i), i < destCapacity and destCapacity > 0 are all false. Nothing is written and the full length is returned, as for a capacity of 0. The existing code already gives that answer once the throw is gone.
  • ICU computes destCapacity - i in int32_t at each escape and before each escape-free segment it appends. For a capacity close to INT32_MIN that subtraction overflows, which is undefined. That case still throws NotImplemented_nid_no_patch, at the points where ICU computes it.

A source longer than INT32_MAX bytes also still throws: ICU truncates the remaining length it passes to u_unescapeAt, and its output count overflows. So u_unescape stays counted as to do. The debt entry now says which cases throw which exception.

tools/progress.py --module libSceIcu: 11 → 12 implemented (u_unescapeAt), 109 → 108 to do.

Tested

Windows 11 Pro 10.0.26200, WinLibs GCC 15.2.0 r7 (x86_64-ucrt-posix-seh), Release, BUILD_TESTING=ON. Written and tested on main 750cf6cf48, then rebased onto d4cb89300f, which changes none of these files; the tests below and check_conventions were rebuilt and rerun on the rebased head, while the control and pr_overlap ran before the rebase.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

…cape capacities

ICU 78.1's u_unescapeAt dereferences the offset and calls the callback without checks, and u_unescape reads the source without one, so a null one faults: throw std::invalid_argument there instead of NotImplemented_nid_no_patch. A negative u_unescape capacity is clamped by _appendUChars and fails every room check, so nothing is written and the full length is returned; only a capacity whose signed difference with the output length overflows, which ICU leaves undefined, still throws.
@github-actions

Copy link
Copy Markdown

Progress report (d4cb893 → 0788324)

📈 System libraries: 46.66% (+0.01%, +1 implemented functions)

✅ 1 implemented
Library Name
libSceIcu u_unescapeAt_nid_postfix

Changed libraries (implemented exports)

Library Before After Change
libSceIcu 11/120 (9.17%) 12/120 (10.00%) +0.83 pp

@adriadam10 adriadam10 self-assigned this Oct 28, 2026

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed exact head 0788324. The ICU behavior claims match the cited implementation: null pointers that ICU dereferences now use the project invalid-argument exception policy, negative destination capacity writes nothing, and cases where ICU’s signed capacity subtraction would overflow remain explicit throws. The tests cover null precedence, unchanged destination bytes, and boundary cases around INT32_MIN. Hosted conventions, intake, eligibility, report, labels and applicable Windows validation pass; platform-specific jobs outside this change are skipped. I have not independently run the Windows test suite, and no console ABI result is claimed. The current-main merge is clean and git diff --check passes.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 12692 docs/dev/debt/libSceIcu.md L10-11

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 11088 docs/dev/debt/libSceIcu.md
PR 11118 docs/dev/debt/libSceIcu.md
PR 11122 docs/dev/debt/libSceIcu.md
PR 11132 docs/dev/debt/libSceIcu.md
PR 11136 docs/dev/debt/libSceIcu.md

@JJenkx

JJenkx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Windows run of this head (07883249e6), merged onto main 3a2b919816 (clean merge, local only). Windows 11 Enterprise 10.0.22621, Ryzen 9 5950X, WinLibs GCC 15.2.0 UCRT r7, Ninja, Release, BUILD_TESTING=ON, no Vulkan driver visible.

  • This PR: guest_icu_unescape passes 3 of 3 (ctest --repeat until-fail:3).
  • Negative control: with core/libs/prx/libSceIcu/Unescape.cpp put back to main's version and everything else from this PR, guest_icu_unescape fails.

AI-assisted: yes (Claude Code).

@adriadam10
adriadam10 merged commit b6f6d2c into boykopovar:main Oct 29, 2026
11 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the null-input and negative-capacity paths against ICU 78.1's ustring.cpp, the overflow points of room() against the new test rows, the APS5_INVALID_ARG_EX use (same as CString.cpp) and the updated debt entry; the new tests fail on main. Conventions and Windows jobs are green; merged.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants