This happens because (paraphrased) the standard dictates that loops without side-effects eventually terminate, and all other programs have undefined behaviour. You can't turn hitting that undefined behaviour into a compiler error, as distinguishing between the two requires solving the halting problem. You can remove the whole assumption, but that limits the optimization capabilities of the compiler. That gives a trade-off: do we want actual infinite loops, or do we want better optimizations? The second seems more useful to me, even though it might sometimes give surprising behaviour for the programmer.
> You can't turn hitting that undefined behaviour into a compiler error, as distinguishing between the two requires solving the halting problem.
That's a fair point, but it still feels wrong to me that the compiler can throw away large chunks of code and change the outcome of the function silently.
Throwing away large chunks of code is what optimizers exist for! Sadly, most discussions of UB only show when it "goes wrong" (has surprising results), not when it goes right!