Arduino freeze due to faulty return type

I recently built my first Arduino project, a program reporting the value of an ultrasonic distance sensor by adjusting delays between beeps much like a metal detector. Here's the code for reference.

During some refactoring, cleanup and optimizations a severe bug snuck in, causing the Arduino to partially corrupt serial communications and completely crash shortly after. This would occur after a relatively short but seemingly random amount of time. After hours of reverting changes I found the culprit: I forgot to change a String return type to void in a function that didn't use a return value any longer. Here's a diff with the bugged version.

I am used to working with higher level, mostly interpreted languages, so this surely gave me a good surprise. I don't understand microcontrollers and low-level programming very well yet, and I'm very curious how such a seemingly trivial issue (nothing is returned anyway, right? An interpreter would just discard the value) caused such a huge mess. I thought it was perhaps a memory leak but I can't think of any logical explanation for what is leaking exactly. Also, I wonder why the compiler didn't complain? Couldn't it have known the microcontroller would choke on this code?

Sadly the Arduino IDE does not show warnings, nor is there any easy way of enabling them, except for turning on verbose compilation. If you turn on verbose compilation you see this:

sketch_aug14a.ino:156: warning: no return statement in function returning non-void

You might also have seen this:

sketch_aug13m.ino:16: warning: control reaches end of non-void function

I can't reproduce the crash (I don't have those libraries) however I strongly suspect that since you have "promised" a String object to be returned, and you have not returned one, that the calling function calls the String class destructor on a non-existent String object. Hence the crash.

Thank you! A very enlightening answer. Not showing a list of warnings after compilation is a ridiculous design choice in my opinion, especially when ignored warnings can have such dire consequences. I understand Arduino is aimed at beginners... but shouldn't they learn how to properly debug too? Anyway, good to know where to look for them from now on. With those warnings tracking down the problem would've been a piece of cake.

Your explanation for the cause of the crash makes perfect sense. I didn't consider that String being an object could make a critical difference. I suppose this would be called something like a 'null pointer exception/crash' and shouldn't occur with a primitive return type? I don't have access to my Arduino gear right now, but I'll try substituting the String with an int later. If the crash is gone I think that'd prove the theory.

Not showing a list of warnings after compilation is a ridiculous design choice in my opinion

Given the target audience of the Arduino, it's quite a sane choice, IMO.

but shouldn't they learn how to properly debug too?

Yes they should, but first, they've got to get code to compile, and a list of obscure warnings ("implied narrowing cast" is likely to be prominent) is going to be a massive turn-off.

Also as a general point I would be wary of using any sort of dynamically allocated memory other than
local vars on the stack, since with a few kB of RAM a memory leak isn't tolerable. String class is a
classic example and there have been bugs with it in past versions.

You have to think "finite" rather than "infinite" when programming a microcontroller like this - its much closer
to the hardware view of the world than the computation theoretic view. With the Mega and Due and other
boards with larger memory model you can afford to be more cavalier with resources.