Describe the bug
Wrapper.Cleanup() unloads the native ICU libraries but leaves every outstanding ICU
handle pointing into the unloaded module. Using any object created before the call — a
RuleBasedCollator, a break iterator, a transliterator — then dereferences freed memory
and takes down the process with an AccessViolationException, which managed code cannot
catch.
Nothing marks those objects as invalid, so the failure surfaces as a hard crash inside
native ICU rather than as ObjectDisposedException.
To Reproduce
Wrapper.Init();
var collator = new RuleBasedCollator("&b < a");
Console.WriteLine(collator.Compare("a", "b")); // 1
Wrapper.Cleanup(); // unloads icuuc/icuin
Console.WriteLine(collator.Compare("a", "b")); // AccessViolationException
Fatal error. System.AccessViolationException: Attempted to read or write protected memory.
at Icu.NativeMethods.ucol_strcoll(SafeRuleBasedCollatorHandle, System.String, Int32, System.String, Int32)
at Icu.Collation.RuleBasedCollator.Compare(System.String, System.String)
Reproduced on ICU 54 and 62, on net8.0 and net48, x64 and x86 — it is not
version-dependent.
Expected behavior
Use of an ICU object after Wrapper.Cleanup() should throw ObjectDisposedException, not
corrupt the process. Cleanup() could track outstanding SafeHandles and invalidate them
before unloading, or at minimum the handle wrappers could check whether the library
generation they were created under is still loaded.
Why it matters
NativeMethods.Cleanup()
unloads both libraries; a later call re-resolves the delegate against a freshly loaded ICU
and hands it a UCollator* from the previous load.
Easy to hit when one component tears ICU down while another still holds objects:
FieldWorks' PUAInstaller calls Wrapper.Cleanup() to regenerate nfc_fw.nrm, while
libpalaso's IcuRulesCollator reuses collators from a static dictionary with no disposed
check.
The stack above is also frame-for-frame identical to #130, open since 2020 as an
intermittent collation crash. I could not reproduce that on any ICU from 44 to 67; this
path reproduces it exactly, and would be intermittent in the field since it depends on the
module's re-mapped base address.
Describe the bug
Wrapper.Cleanup()unloads the native ICU libraries but leaves every outstanding ICUhandle pointing into the unloaded module. Using any object created before the call — a
RuleBasedCollator, a break iterator, a transliterator — then dereferences freed memoryand takes down the process with an
AccessViolationException, which managed code cannotcatch.
Nothing marks those objects as invalid, so the failure surfaces as a hard crash inside
native ICU rather than as
ObjectDisposedException.To Reproduce
Reproduced on ICU 54 and 62, on net8.0 and net48, x64 and x86 — it is not
version-dependent.
Expected behavior
Use of an ICU object after
Wrapper.Cleanup()should throwObjectDisposedException, notcorrupt the process.
Cleanup()could track outstandingSafeHandles and invalidate thembefore unloading, or at minimum the handle wrappers could check whether the library
generation they were created under is still loaded.
Why it matters
NativeMethods.Cleanup()unloads both libraries; a later call re-resolves the delegate against a freshly loaded ICU
and hands it a
UCollator*from the previous load.Easy to hit when one component tears ICU down while another still holds objects:
FieldWorks'
PUAInstallercallsWrapper.Cleanup()to regeneratenfc_fw.nrm, whilelibpalaso's
IcuRulesCollatorreuses collators from a static dictionary with no disposedcheck.
The stack above is also frame-for-frame identical to #130, open since 2020 as an
intermittent collation crash. I could not reproduce that on any ICU from 44 to 67; this
path reproduces it exactly, and would be intermittent in the field since it depends on the
module's re-mapped base address.