Android Girl Adventures: Overly Urp-timizedNSFW

How a funky JVM optimization resulted in our Android girl belching a bunch instead of telling us what was going on, and failing her self-test accordingly.

Author's note: I explained this bug to my friends in vore terms and I'm very proud of it.

(lightly edited from the original Discord log)

Plushie Artist/Collector Today at 6:38 PM

o h my g o d

this is the best bug I have ever heard

and I feel like yall need to appreciate it so I'm gonna EXPLAIN

So let's use a vore metaphor to explain this.

A DigestedGirl is many types of things. She's a Girl, she's ThighFat, BoobFat, BellyFat...

So if you have a DigestedGirl and you need BellyFat, you can just say "Take this DigestedGirl I have and use her as BellyFat." She's still the same DigestedGirl, but now you're using her in a different way - now you can only do things with her that you can do with BellyFat, not all those other things.

Now, an UndigestedGirl is a girl, but she is not BellyFat. So if you try to turn her into BellyFat, she'll complain, "I'm an UndigestedGirl, not BellyFat!"

If you just have a Girl, you can still try to turn her into BellyFat, even though not all Girls are BellyFat. But there's a chance you'll fail, if she's an UndigestedGirl.

I mean, give her time. But anyway.

(For those interested in the non-metaphor, in our Java code: The type of thing you are is your class. Taking one thing you're holding and using it a different way is called casting. Trying to use something as a class that it's not is a ClassCastException.)

So we have a self-test of our cute Android. We have her ingest an UndigestedGirl, and then process her contents into BellyFat. Then she expects herself to say: "Indigestion detected. Reason: *mocking tone* I'm an UndigestedGirl, not BellyFat!" If she doesn't say that, she reports that she has a glitch.

(In other words, we have a test where we're asserting that our attribute-map code throws an exception with a specific message, and that it has a cause which is a ClassCastException with another specific message.)

Here's the problem. Sometimes, the self-test is failing. Because the Android instead says "Indigestion detected. Reason: uuuuuUUUOOOOOOOOOOAAAAAAAARP."

And we can't tell whether she understood what she was being asked to do or not.

(In other words: The inner exception had a null or truncated message, causing the assertion to fail.)

And it's not just randomly sometimes. It's every time... UNLESS we also tell the Android to go do an unrelated complicated thing, in a completely different part of her self-test.

OR, if we ONLY tell the Android to do this one part of her self-test.

(read: Deleting a specific heavyweight method invocation in a specific other test causes the test in question to fail, but not running that other test at all causes the test in question to pass.)

So, we read up on the user manual, and it turns out there's a switch on the Android's back that determines whether she bothers listening to prey or not. When the switch is ON, she listens to prey carefully and explains what they say. When the switch is OFF, she just belches in your face, because that's appropriate. Bad Bot-chan.

(read: There's an optimization, -XX:+OmitStackTraceInFastThrow where certain special thrown exceptions like NullPointerExceptions or ClassCastExceptions use preformed instances which do not have stack traces or messages, in order to throw those exceptions more efficiently - collecting stacktraces and building exception instances is expensive. The truncated message mentions -XX:-OmitStackTraceInFastThrow, which is how you turn that optimization off.)

Okay, the switch is OFF, that's the default! But... Then why does she sometimes take the time to listen to her prey?! Why does her self-test sometimes pass?!

My theory - which so far seems to make sense, is this.

She only ignores her prey when she goes on autopilot. When she has to think about what to do, she has time to listen, and she explains what's going on in case she needs to report it to her programmers. When she's on autopilot, she just belches quickly to get it out of the way and then keeps going.

When we ask her to do something else, she has to stop and think about how to do this other thing, so she goes off autopilot.

When we only ask her to turn her stomach's contents into BellyFat, she has to stop and think about it.

But when she has to do all the tests EXCEPT that one other test, she has to turn her stomach's contents into different things over and over and over again, so she just puts herself on autopilot and zones out, ignoring the cries from her conversion chamber and belching if she gets indigestion.

(read: Java has a concept of just-in-time compilation, where code that would normally be interpreted is turned into much faster native code. The optimization only triggers on just-in-time compiled code, not interpreted code. And code only gets just-in-time compiled when it's a hotspot, when it gets used repeatedly and not too much else is going on. So having the other test interrupts the just-in-time compiler's optimization detection, causing it not to compile that code. And ONLY having the one test means the just-in-time compiler never has time to detect that this code needs optimizing in the first place. But when the complicated test is absent but the other simple tests are present, it is detected as a hotspot.)

The solution? When the prey starts complaining, just ignore what she says and tell your master what happened. Then her self-test can check that! Both of these work just fine!

"Indigestion detected. Tried to turn UndigestedGirl into BellyFat. Reason: *mocking tone* I'm an UndigestedGirl, not BellyFat!"

"Indigestion detected. Tried to turn UndigestedGirl into BellyFat. Reason: uuuuuUUUOOOOOOOOOOAAAAAAAARP."

(read: We just add our own version of the extra actual/expected type info in the ClassCastException's message to the wrapper exception's message, and then assert on that instead of the CCE. This doesn't get optimized out, since the optimization only affects the special exceptions, so the test passes regardless of whether the JIT compiler gets activated.)

As a reward for her being so good during all this, we fed her an extra programmer.

(read: um... help!)