I see your point now. Yes, Java won't segfault, unlike Go on slices/maps/interface values. But even without segfaults, undefined behaviors due to race conditions can still be pretty in Java (causing infinite loops, memory leaks, etc.). Maybe it's better to let the program crash with a segfault.
"Undefined behaviour" has a specific technical meaning. In Go, data races can result in UB. In Java, they can't.
Java may be non-deterministic, or exhibit unintended behaviour in response to a race condition. UB is strictly worse because it could do either of those things... or indeed anything else, segfaulting being the least of your concerns.
This is the "technical meaning" of "undefined behaviour" according to Wikipedia:
> Undefined behavior (UB) is the result of executing a program whose behavior is prescribed to be unpredictable, in the language specification to which the computer code adheres.
Can we predict the behavior of a Java program with a data race?
A section of Java code with a data race can’t break anything that the same section of code couldn’t have been rewritten to intentionally break without using a data race. For example: it might throw an exception, loop infinitely, or return valid values that the programmer didn’t expect; but it cannot corrupt the internal state of the JVM, violate type-safety, fiddle with private fields, or access data that isn’t reachable from its own variables. Data races cannot be used to violate a security barrier between mutually untrusting sections of Java code.
You're right. That's a big advantage of the JVM. It's memory safe even in presence of data races.
That post from Russ Cox (again) explains quite well why Go is not memory safe in presence of data races, and what should be changed to fix this:
https://research.swtch.com/gorace
This depends on what "unpredictable" means. Do I know precisely what this java program will compute? No. But I do know some things that it won't do. Its behavior is not completely unbounded.
That's a really bad standalone definition. It would apply to randomly generating numbers, or most attempts at allocating memory and then sorting by address.
Others said this different ways, but I think the scenarios where you don't get an immediate segfault at the bad read/write are the trickiest ones--hard to debug, at least. One flavor is, say, writing past the end of a slice b/c you read the pointer from one slice and len/capacity from another. If it segfaults, it might be later when the clobbered object is next used.
That's where I start dreaming about best-effort detection of races in production binaries or even just reducing the chances of torn reads. Years back there might have been other options, like explicit private vs. shared heaps with more controls on the shared heap, but there seems to be a more restricted set of choices now.
I agree that most of the time the segfault will happen later during program execution, which makes debugging very tricky. From that point of view, Java preserving memory safety even in presence of data races is a big win. I guess that's also what makes Rust so appealing for programs needing memory safety with concurrency and no GC.
No it is not better. Java will preserve the integrity of the runtime. Still memory safety issues in go due to races are probably very hard to exploit so it might not make a huge difference in practice.
Yes, the JVM will keep executing the program, but I'm still not convinced this is strictly better than segfaulting if a thread is stuck in an infinite loop using CPU for nothing, and another is leaking memory.
The problem is, UB is not guaranteed to segfault. A guaranteed segfault when a bug is hit would be great - but randomly getting one of (segfault, silent memory corruption, invalid instructions, ...) is not great, and most people would consider that to be much different than what Java gives you.
I agree that a data race on an interface value in Go, is worse than an infinite loop or a memory leak, because it can lead to calling the wrong method on a value, which is hard to debug.
> We have to admit that exploiting this requires a fairly specific situation in which there is a data race we could trigger and some structs with function pointers around.
I would note that Java is designed to run untrusted code, so fat pointers would be unacceptable as the attacker could easily craft the code required to trigger the race. Go does not claim to provide sandboxing of untrusted programs.
https://stackoverflow.com/questions/51102644/what-the-worst-...