1. Use-after-free (C, C++). This is a logic bug and a security problem.
2. "Logically" dangling pointers (safe languages with non-generational indices). This is a logic bug, but not a security problem.
3. A memory leak (safe languages with garbage collectors). This is a bug that can be difficult to track down.
4. Panic/exception (safe languages with generational indices or weak pointers). This is a runtime failure.
I'd argue that, of these four options, (4) is generally the best. The ideal would of course be a static guarantee instead of any of these. However, static guarantees always come with restrictions of some kind. For the truly unrestricted case, in which your data references are completely irregular, I'm not sure you can really do better.