Earlier quoted context omitted.
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".
afaik the person you replied to is wrong, using System.arrayCopy would definitely make a copy and not be optimized away. Maybe poster meant only copying the reference with `char[] copy = original` which indeed does nothing, but that's not a defensive copy.
Breaking java.lang.String
141–150 of 206 posts
Re: Breaking java.lang.String
#142Earlier quoted context omitted.
How would you fix this? Possibly by copying the input array to a local array as the first step? But then you might copy a lot of data, which is in 99% of the cases totally unnecessary. Have a separate threadsafe and a non-threadsafe constructor for char[]? EDIT: another idea is to do a hash of the input array, and compare between start and end. But that also requires an O(n) walk through the input...
> But that also requires an O(n) walk through the input... Compressing requires an O(n) walk through the input either way.
Re: Breaking java.lang.String
#143Earlier quoted context omitted.
That's instantiating a List, not an array.
As you would expect in a vaguely modern language, Java's "List" interface is typically backed by a growable array as the type ArrayList. Only people who have no idea about caches would think List should necessarily be some sort of Linked List type. You would use Collections.unmodifiableList to make it unmodifiable.
Re: Breaking java.lang.String
#144Calling 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…
This bug can change the meaning of code in third party libraries by altering the set of interned strings. If any input to a program can be converted into this race, then it becomes a security vulnerability. It would already be a security vulnerability in the old Java sandbox model.
There was a little optimization post a few days ago: https://news.ycombinator.com/item?id=36618344. Basic summary of the program, given a NUL terminated buffer like "spppssss", return an int where each 's' adds 1, and each 'p' takes 1 away. Several of the alternate implementations in the HN comments involved using strlen first, which has the nice of effect of being highly vectorized, and knowing the size, the compiler can better vectorize the actual logic loop, and a human can vectorize better still. If we imagine another piece of code that concurrently modifies the input buffer to write a NUL to the 0 index, it would change the length to 0. To be generous and to simplify the discussion, let's say the concurrent modification happens to become visible to our thread after strlen but before the following loop. Is there now a bug in this implementation?
If the point is that java.lang.String must ensure its pre-conditions and post-conditions are always maintained, because it's so fundamental to other security functions of the JVM, then there is no solution that allows for untrusted/buggy shared memory concurrency. Any suggested modifications to the constructor like "add a lock" or "copy the buffer first" will not work, and are missing the point entirely.
If the issue is the string intern-ing of such a dodgy String, then the best you can do is code intern() more defensively, to throw out detectably dodgy input. I could then understand saying intern() has a bug, but I don't think that was the point of the post, since there is no discussion of the intern() code.
Re: Breaking java.lang.String
#145Earlier quoted context omitted.
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".
afaik the person you replied to is wrong, using System.arrayCopy would definitely make a copy and not be optimized away. Maybe poster meant only copying the reference with `char[] copy = original` which indeed does nothing, but that's not a defensive copy.
Whether the JVM actually does this for arraycopy is an implementation detail. The JVM is permitted to perform this type of escape analysis and elision, and definitely does for some allocations.
In general arrays and concurrency doesn't mix very well in Java, and is something I'd avoid mixing on principle. You avoid most of these types of problems by having threads talk by passing around immutable objects via concurrency primitives, volatile references, synchronized-blocks, etc.
Re: Breaking java.lang.String
#146Earlier quoted context omitted.
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
#147Earlier quoted context omitted.
Yes. Java promises (unlike C, C++, Go, etc.) not to have Undefined Behaviour as a result of data races, however you do lose Sequential Consistency, which means humans aren't able to successfully reason about non-trivial programs with data races. More specifically Java says something like, if you race a value of some sort, even a complex value like a hash table, it doesn't get fatally damaged, but its new state is som…
Purely out of curiosity, is that much better? At least the data won't be complete garbage, but there'll still be a logic bug lurking in the details.
It's not Good that the Java code has a bug you don't understand, but it's very unlikely to have completely wild outcomes like giving Jimmy a massive pay rise.
When they began this work, as I understand it the Java engineers imagined humans can successfully reason about the resulting bugs, but turns out that's not true. So this work might have been deemed unnecessary if they'd known that but obviously it's hard to know without trying.
Re: Breaking java.lang.String
#148Earlier quoted context omitted.
Not if the memory has been allocated on a shared memory segment, Rust has no control over what other processes might do.
Sound Rust code would either make functions touching the shared memory marked unsafe, or would do a defensive copy out of shared memory.
Re: Breaking java.lang.String
#149Earlier 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…
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 security vulnerability which can't be fixed by guards at the interning step.
Re: Breaking java.lang.String
#150Earlier quoted context omitted.
For what it's worth, Java does at least give some guarantees in case of data races -- the observed value will always be one that was explicitly set by one thread. This is different from most other languages, e.g. in C,C++, unsafe Rust it is UB. Of course it can and still will result in invalid states.
If you read a double or long while writing to it from another thread, you are not guaranteed to read a value that was ever explicitly set because they update 32 bits at a time rather than atomically.