I think I've found a workaround.
This is clearly not an NGUI bug. It's not even a Unity bug. The Adreno 320 / 330 GLES2 driver is at fault here. Here's some folks who are not using Unity at all that have hit this crash:
https://github.com/opensciencemap/vtm/issues/52Anyway, the crash callstack is proceeded by some spew from the driver like this:
04-11 13:41:21.927: W/Adreno-GSL(12206): <ioctl_kgsl_sharedmem_write:1668>: kgsl_sharedmem_write:invalid arg offset 49152 size 1536 memdesc size 4096
and the crash itself is in memcpy, so it looks like this has something to do with moving verts to a buffer that isn't sized correctly. Or something.
On one of my test devices I was able to get the crash to occur at a particular point, and then by manipulating unrelated things, I actually moved the point of the crash about 2 seconds into the future (i.e. it took 2 seconds longer to occur than the normal trigger point). This suggests to me that this might be some sort of cache buffer overflow in the driver; perhaps error accumulates until finally the crash occurs.
On the theory that reducing the number of vbo size changes might placate the driver, I commented out the line that Aren indicated above (setting trim if the number of verts is < 50% of the buffer in UIDrawCall). This probably wastes some memory, as we have some VBOs that are allocated for many more verts than they need, but on the other hand if we have a case where the contents of the VBOs change rapidly, and if the driver dies because of an accumulated overflow, this might be sufficient to avoid the crash.
So far it appears to work. I've run for about an hour on the Galaxy S4 with zero crashes (before this change I could repro in < 1 minute). I need to do some more testing on other devices, but for now it seems like a pretty benign way to work around the crappy driver.