You are missing others: the toolset is so complex even the designers of the toolset don't understand the tool they are creating.
No, I don't think I am. There is no tool that covers every possible situation or circumstance. Horses for courses, and source-level debugging is not a tool for fixing toolset complexity issues. All you're concerned with is: does the code do what I think it should, and single stepping the source around the probably area is a good first step. That will most probably show you the code error (whatever the language is), but if not it may well point to a deeper problem. That's when you start furkling with toolchains and stuff.
And despite weak spots in language specification, toolchain creators will have to implement defined behaviour. Only problem may be different choices which affects portability between compilers but this is already a problem due to using different pragmas, compiler specific defines and attributes. So as usual a weak spot in a specification is solved by what is industry standard behaviour.
The defined behaviour has a tendency to change over time, as the compilers become ever more "capable".
Undefined behaviour also changes over time and exposes more problems, as the compilers become ever more "capable".
I don't think that is 100% true. For example: there is some abiquity where it comes to how the << and >> work in C. The way compiler makers have implemented these is a way which works as expected. Nobody with a sane mind is going to change this behaviour because it would break a huge amount of code.
Most problems with incompatibilities I've encountered are with header files, pre-defined values and things which used to threw a warning becoming an error.
Breaking working code/applications is unpleasant wherever it occurs. It is a traditional problem with C and C++. Other languages cause zero or far fewer problems in that way.
Previous/historic code presumed the language definition (and language ambiguity) in force at that time. Not unreasonable.
Compilers have, over the years, become more aggressive at interpreting the language specification to the compiler's advantage, in the name of gaining minor performance enhancements. That breaks some historic code which used to work, sometimes to the point of omitting to generate the object code! [1]
The traditional response is to blame the developer, implicitly for not taking account of what might happen in the future. Personally I think that is disreputable; the tools are the problem.
[1] That has been a problem with some
well-known benchmarks; later versions of the compiler have run the benchmarks "infinitely" fast. Some compilers long before VolksWagen and Toyota - detected they are compiling the benchmark, and emitted unjustifiably optimised code .