This is exactly why java needs frozen arrays [1]. The safe thing to do is freeze the array before doing anything with it. Then, you can rely on COW to copy to the array if someone is modifying it concurrently with you reading it. In the general case, you'd have fast string creation and in the tricky case you simply pay the clone cost as a penalty for being dumb. [1] https://openjdk.org/jeps/8261007#:~:text=How%20do%2…
Breaking java.lang.String
161–170 of 206 posts
Re: Breaking java.lang.String
#162Re: Breaking java.lang.String
#163This is exactly why java needs frozen arrays [1]. The safe thing to do is freeze the array before doing anything with it. Then, you can rely on COW to copy to the array if someone is modifying it concurrently with you reading it. In the general case, you'd have fast string creation and in the tricky case you simply pay the clone cost as a penalty for being dumb. [1] https://openjdk.org/jeps/8261007#:~:text=How%20do%2…
I would love to have this in Java, hopefully this JEP makes it!
A real common example of this is `Enum#values`.
Ideally (IMO) this applies some aggressive COW operations. So perhaps internal to the enum you have a frozen array of the values and for "values()" you return something like `VALUES.unfreeze()` which points to a transparent unfrozen array. On a write action, you'd copy the array but in the general case you'd simply read from the frozen array until someone does something dumb.
You could take it a step further and simply expose the `values` field or add a new "frozenValues" method to not break existing code. In either case, you'd end up with faster performance because the JVM isn't copying the internal array needlessly.
Re: Breaking java.lang.String
#164It is possible to fix this String constructor implementation without creating a defensive copy of the input array or having a TOCTOU vulnerability. // Change this implementation to a loop. public String(char[] value) { while (true) { byte[] temp = StringUTF16.compress(value); if (temp != null) { this.value = temp; this.coder = LATIN1; break; } temp = StringUTF16.toBytes(value); if (temp != null) { this.value = temp;…
An unconditional loop with no guarantee of forward progress may loop indefinitely, and hence, is not a sensible solution to the problem.
Re: Breaking java.lang.String
#165Earlier quoted context omitted.
Sound Rust code would either make functions touching the shared memory marked unsafe, or would do a defensive copy out of shared memory.
That safe layer around unsafe still has no way to validate the consistency of the data.
If you do your validation during accesses it's fine. If you copy the data out of the shared memory it's fine.
Or you could use a mutex to protect the data between validation and use.
If you're worried about another process editing the memory without taking the mutex, that's equivalent to worrying about other unsafe code editing the memory without taking the mutex. The solution is the same in both place: don't share memory with completely arbitrary code. When people compare languages and techniques, they (rightfully) assume you're not doing that.
Re: Breaking java.lang.String
#166Earlier 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…
> After a defensive copy of the byte-array is made, just before returning the newly constructed string object, verify once again that the chosen coder matches the string contents. Allocating new String objects might very well be the most frequent memory allocation operation in the JVM. IMO it would be a mistake to do anything to slow this down to protect against this weird instantiation behavior, unless it implies a…
Compared to this, I wager that the incremental cost is small to verify that the coder matches the content.
Also, depending on the implementation of string-interning and StringUTF16.compress, it's possible that the verification step is only needed for non-LATIN1 strings. Which according to the JEP is only a small minority of strings seen.
If there is a cheaper solution, I'm certainly all for it. I'm just spitballing ideas here. But I don't think it is acceptable to allow "hello world".startsWith("hello") to return false.
Re: Breaking java.lang.String
#167Out of interest, how should this be handled? Is this a bug in Java which should be fixed (looks like that to me)? My understanding was Java generally doesn't do "you did an undefined behaviour, so it's your fault", except for specifically marked very low-level interfaces.
I can only think of a couple of ways to fix this, none of them ideal from a performance perspective: - Make a defensive copy of the passed-in character array, which would be immediately discarded when it is encoded to bytes. This basically sucks given how often String creation happens in a typical Java program. - Dispense with the whole use of the coder to check for non-equality of Strings, and insist on a character-…
Is comparing two strings of the same length but different encodings all that common?
Re: Breaking java.lang.String
#168Earlier quoted context omitted.
An unconditional loop with no guarantee of forward progress may loop indefinitely, and hence, is not a sensible solution to the problem.
It only loops if you modify the string in certain ways partway through the loop. Is that a significant problem? As soon as you stop your indefinite loop of race-condition writes, this loop is guaranteed to finish.
This is basically a very poor man's version of software transactional memory. Noticing this is one step on the road to realizing that shared memory concurrency needs cooperative synchronization (and locks are just one way to achieve that), and most important of all, you should strictly limit the number of functions that need to synchronize at all, by strictly limiting the number of shared data objects.
I think the article OP and many in the comments here have taken the wrong lessons from this. I think the real lessons are:
1. In a program containing data races, one cannot assume objects obey their stated invariants.
2. Therefore, security/correctness in a shared memory concurrent system cannot be achieved if there is untrusted/unverified code (i.e., code that may introduce data races).
3. Regardless, it may pay dividends to try harder to learn from 1 and do better at validating input, especially in silently pervasively used shared state (e.g., the string intern table). Unfortunately, I think this will always be best effort.
Re: Breaking java.lang.String
#169Earlier quoted context omitted.
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.
So they should add that mutating arguments during object construction may lead to not-so-successfully constructed (invalid) object. __Mission failed successfully__
Re: Breaking java.lang.String
#170Earlier quoted context omitted.
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.
Functions have an assumed pre-requirement not explicitly spelled out in every single javadoc, that the program does not have a data race. May as well ask for every javadoc to explicitly include a pre-requirement that no cosmic rays flip bits of memory. But to be serious again, I'm sure if you look at Array or whatever type is involved in char[], you'll find that it is explicitly marked as not thread-safe.
Maybe in your programs, not in mine. I want my classes’ instances to fulfill their contract once it has been constructed. If it can’t do so, construction should fail with an exception.
Note also that for example the OpenJDK’s String::hashCode implementation is formally not data-race free (racy single-check idiom), so your assumption as stated doesn’t hold.