Live data from Hacker News

Breaking java.lang.String

wouter.coekaerts.be

121–130 of 206 posts

Re: Breaking java.lang.String

#121
post #77

Earlier quoted context omitted.

MaybeUninit ::assume_init() is the unsafe call. The promise that this is actually a T and not just a T-sized blob of uninitialized memory is made by that unsafe call, and since it's unsafe it's your responsibility as the programmer to obey the constraint, that this is, in fact, an initialized T. Depending on how long "a long time" is, you might well not actually have been thinking of MaybeUninit. The prior solution (…

The point I was trying to convey (badly) with this example is that in both the assign to raw generic and assume_init case, the developer overrides the compiler's knowledge for his/her, demonstrating that most languages have similar escape hatches, because they are sometimes simply needed.

Yes, what you've described is usually unsound†, which is why it's deprecated (but it's offered in the standard library and so Rust policy is not to remove it since there are at least in theory sound ways it could have been used, shame to break them).

MaybeUninit doesn't override the compiler's knowledge. It's using one very, very clever trick. Take a look at how MaybeUninit is defined. It's a union! The compiler can see this might be a T, but it can't ever see whether it's actually a T right now. As a result all the mis-optimisations which occur with the unsound approaches can't happen. MaybeUninit::assume_init() is just a union read, which, sure enough, is an unsafe operation in Rust (writing to a union is safe, but reading from one is not) and that's why the function is unsafe.

† This is technically sound for Zero Size Types, because then we're basically saying e.g. "You see nothing? Well, I promise it's actually no Spoons, like, an array of 0 Spoons". So that can't cause the compiler to emit code that we didn't expect - the Rust compiler isn't going to emit code to read or write no Spoons, whether or not they "exist" according to type analysis.

Re: Breaking java.lang.String

#122
post #111

Earlier quoted context omitted.

Dunno, this seems like you're violating the java memory model and then then fully expected weird shit happens. If you're mutating a shared state between threads without proper synchronization, and there is no way for String to do this on its own, the CPU is permitted to do stuff like out-of-order execution as there is no happens-before relationship between the write and the read. The JVM is further permitted to optim…

I didn't think it was a bug either till I got to the "Spooky action at a distance" section. The fact that the following code: "hello world".startsWith("hello") can return false in any circumstance, is a bug in the language. The fact that some other code in an entirely unrelated part of the codebase can intern a broken string and thus break string-comparisons for the entire codebase, is mind boggling. Fortunately, I d…

A defensive copy probably doesn't help here. The JVM is permitted to optimize it away, since it has no externally observable effects and doesn't have a constructor.

Re: Breaking java.lang.String

#123

Calling this a "bug in java.lang.String" is silly. The same "bug" exists for all functions that take mutable objects. If you take a map and lookup two different keys, yep, that's a "bug". The bug is the other piece of code that introduces the data race in the first place. You can argue the case for languages like Rust with it's borrow system, or others that use linear types or something along those lines, to eliminat…

[deleted]

Re: Breaking java.lang.String

#124
post #96

Earlier quoted context omitted.

Thread safety of char[] is the cause of the bug, but the effect is that String instances can violate their invariants, which they shouldn't even in case of thread-unsafe code.

In my opinion the abstraction is leaky but everything works according to the spec. I might tell you about another "bug" and "invariant violation" which is possible but is not a bug. Try to use Sets or Maps with keys having broken hashcode.

I can't agree with this analogy. This is not a defective user implementation of hashcode; it is a defective platform implementation of equals (and starts with, etc.).

Re: Breaking java.lang.String

#125
post #110

Earlier quoted context omitted.

Eh, I don't think the author is to be ridiculed. We're all wrong from time to time, I'm wrong in most of what I say, I'm sure you've been wrong on occasions, this is fine. The author's views on how concurrency works is very common and taught in schools and textbooks, but also quite inadequate. Instead let's take this opportunity to talk and teach about the JMM and deepen the collective understanding of the many unint…

Maybe there could be a JMM 2.0 where at least some of the unexpected behaviour is removed. Maybe even at the cost of performance. If something is misunderstood by >99% of the average programmer population then clearly it is not the best solution to the problem.

If that many programmers don't understand the JMM, it's because that many programmers haven't looked at it.

It is the key to reasoning about Java concurrency. Any other model that is not the JMM is flat out wrong. It is virtually impossible to write correct concurrent Java without understanding the memory model.

Re: Breaking java.lang.String

#127
post #91

Earlier quoted context omitted.

Could you provide a link to the documentation to proof it's a bug? The author could call it 'probably bug' or 'misleading String constructor documentation' but instead they state it's a bug without any proofs.

Here: https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-831190... It was accepted as a bug. I'm not saying I would condemn anyone to the death penalty based on that but being accepted as a bug there has to have a significant weight attached to it.

> It was accepted as a bug.

I know. It was in this thread above. So there is no link to the specification to proof that it's a bug.

Re: Breaking java.lang.String

#128
post #87
post #70

Earlier quoted context omitted.

It’s a bug for constructor functions, in my book. I certainly always code defensively to prevent such misbehavior. A successfully constructed object should obey its documented interface contract, period.

> A successfully constructed object should obey its documented interface contract, period. Could you provide the contract you are talking about?

Yes, the contract of String::equals: The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as this object.

The article constructs two String objects representing the same character sequence, for which however equals() returns false, in violation of the above-quoted contract.

Re: Breaking java.lang.String

#129
post #111

Earlier quoted context omitted.

I didn't think it was a bug either till I got to the "Spooky action at a distance" section. The fact that the following code: "hello world".startsWith("hello") can return false in any circumstance, is a bug in the language. The fact that some other code in an entirely unrelated part of the codebase can intern a broken string and thus break string-comparisons for the entire codebase, is mind boggling. Fortunately, I d…

A defensive copy probably doesn't help here. The JVM is permitted to optimize it away, since it has no externally observable effects and doesn't have a constructor.

Wait, really? It's very externally observable in the presence of multithreading. Surely there must be a way to say "no, really, please make a copy".

Re: Breaking java.lang.String

#130
post #74

Earlier quoted context omitted.

Arguably, C++ arguably already solved that specific problem with const safety. I was flabbergasted back in 1995 that Java had dropped that notion.

I don't think it does. In C++ taking a const argument just means that you can't change it. But since the caller can pass non-const as const there is nothing stopping them from mutating it.

You can also use const_cast to remove the const-ness. You could then mutate the value.

I believe that's undefined behavior territory (the mutation, at least in most cases), but I'm sure someone is doing it in the wild.

Post reply on HN