Live data from Hacker News

Breaking java.lang.String

wouter.coekaerts.be

41–50 of 206 posts

Re: Breaking java.lang.String

#41
post #12
post #4

Earlier quoted context omitted.

Mutexes etc ... exist in Java.

What about Rust’s borrow checker (affine types) enforces the use of mutexes (or other sync prims) here?

As sibling comments point out, mutexes aren't needed here. But to answer your direct question, Rust's type system enforces the use of mutexes to access protected values (if you're using the stdlib Mutex implementation) by only allowing access to protected values through a MutexGuard object which is created by locking the mutex. The borrow checker enforces you can't access the MutexGuard concurrently, so therefore you can't access the protected value concurrently.

Re: Breaking java.lang.String

#42
post #35
post #9

Earlier quoted context omitted.

This is a heavily optimized system library - you don’t use mutexes here. Rust wouldn’t help here, if mutexes would be fine, they would have been used. Especially that this is the result of C++ and Java code simultaneously. Hell, it’s probably one area where rust’s benefits are a “hard sell” — you would have to constantly be in unsafe rust manipulating pointers manually as the compiler can’t reason statically about wh…

Rust absolutely helps here because in Rust it’s simply impossible for someone else to mutate something concurrently to you holding a reference to it. Code equivalent to that in the article simply won’t compile in Rust. This is, like, the very point of Rust’s borrow system. You can share, xor you can mutate, but not both at the same time. This holds equally for single and multi-threaded code.

In safe Rust, that is. For unsafe Rust, I don't know exactly which bets are off but it's more than none.

Re: Breaking java.lang.String

#43
post #35

Earlier quoted context omitted.

Rust absolutely helps here because in Rust it’s simply impossible for someone else to mutate something concurrently to you holding a reference to it. Code equivalent to that in the article simply won’t compile in Rust. This is, like, the very point of Rust’s borrow system. You can share, xor you can mutate, but not both at the same time. This holds equally for single and multi-threaded code.

In safe Rust, that is. For unsafe Rust, I don't know exactly which bets are off but it's more than none.

In unsafe rust this is a concurrent modification of an object with shared references, which is an UB.

Re: Breaking java.lang.String

#44
post #9

Earlier quoted context omitted.

This is a heavily optimized system library - you don’t use mutexes here. Rust wouldn’t help here, if mutexes would be fine, they would have been used. Especially that this is the result of C++ and Java code simultaneously. Hell, it’s probably one area where rust’s benefits are a “hard sell” — you would have to constantly be in unsafe rust manipulating pointers manually as the compiler can’t reason statically about wh…

No idea why Thaxll and the other comments are mentioning mutexes. The equivalent (*) API to this Java API in Rust does exist; it's `String::from_utf8(Vec ) -> String`. And the bug in TFA does not exist there. Since the signature consumes the `Vec ` it's impossible for the caller or any other code to still have access to it to be able to modify it concurrently. Also consider the similar API `str::from_utf8(&[u8]) -> &…

The problem here is that we don't want a mutex. Once you have it the performance cost would apply in runtime. In fact, to write this code in rust you would need to write unsafe code to get around the problem where Rust forces you to write correct but inefficient code.

This code is intentionally not thread-safe. This isn't so much a bug but an interesting thought experiment.

Re: Breaking java.lang.String

#45
post #20

Earlier quoted context omitted.

That's true, but in the case of Strings in particular they are generally considered to be thread-safe by virtue of being immutable (and the Javadocs themselves say this in many places). Concurrently modifying the input character array may seem like willful abuse in this case, but I suppose there might be some carelessly written code out there which does it and the post shows how it would create some weird and very ha…

As the article points out, the only thing the Javadoc guarantees is the subsequent modifications of the character array have no effect. It says nothing about concurrent modifications. The type whose thread safety is in question here is not actually String, but char[]. I'm not going to say it is always wrong to share char[] between threads (as a primitive array of something other than double and long, the Java Spec do…

While you are technically right in all your points, I think you exaggerate the problems regarding Java’s type system. Arrays are indeed a pain point from different times (the mistake of their covariance even “infected” C#), but generics are not problematic at all. In fact, code that compiles without warning is guaranteed to never get a ClassCastException.

Every language’s type system gives you escape hatches, so that last example is similar to how one might implement an optimization library in Rust where you have uninitiated elements. Hell, there you don’t even get runtime errors if you get it wrong, it will just segfault. So I really disagree with this notion of “it even worse with generics”.

Re: Breaking java.lang.String

#46
post #9

Earlier quoted context omitted.

This is a heavily optimized system library - you don’t use mutexes here. Rust wouldn’t help here, if mutexes would be fine, they would have been used. Especially that this is the result of C++ and Java code simultaneously. Hell, it’s probably one area where rust’s benefits are a “hard sell” — you would have to constantly be in unsafe rust manipulating pointers manually as the compiler can’t reason statically about wh…

No idea why Thaxll and the other comments are mentioning mutexes. The equivalent (*) API to this Java API in Rust does exist; it's `String::from_utf8(Vec ) -> String`. And the bug in TFA does not exist there. Since the signature consumes the `Vec ` it's impossible for the caller or any other code to still have access to it to be able to modify it concurrently. Also consider the similar API `str::from_utf8(&[u8]) -> &…

The scenario I was imagining and commenting on was about “implementing a JVM with Java’s semantics in Rust”. Of course if we limit the language itself to safe Rust, we get data race freedom, but at a quite significant price for a high level language (it constraints possibly correct programs down a lot). But Rust would not help with relation to the primitives here at all (implemented in C++/Java).

Re: Breaking java.lang.String

#47
post #38
post #34

Earlier quoted context omitted.

Right, that’s my understanding. But OP and a sibling thread here seem pretty sure about the mutex thing. I think there’s some nuance, but not in the general case. Shared memory, lazy statics in async blocks, and asynchronous constructors might have different initialization order mechanics that would require synchronization — but even then, the borrow checker would at least point it out

Without a mutex, you can’t even write code equivalent to that in the article because you cant mutably share as you pointed out. With a mutex you could – and the mutex would prevent data races (but not race conditions in general) – but indeed mutexes are a red herring here (at least in the specific sense of a runtime synchronization primitive). In Java you can’t synchronize defensively because synchronization requires…

Unfortunately I can no longer edit my comment, please have a look at my reply: https://news.ycombinator.com/item?id=36690710

Re: Breaking java.lang.String

#48
post #20

Earlier quoted context omitted.

That's true, but in the case of Strings in particular they are generally considered to be thread-safe by virtue of being immutable (and the Javadocs themselves say this in many places). Concurrently modifying the input character array may seem like willful abuse in this case, but I suppose there might be some carelessly written code out there which does it and the post shows how it would create some weird and very ha…

As the article points out, the only thing the Javadoc guarantees is the subsequent modifications of the character array have no effect. It says nothing about concurrent modifications. The type whose thread safety is in question here is not actually String, but char[]. I'm not going to say it is always wrong to share char[] between threads (as a primitive array of something other than double and long, the Java Spec do…

"The type whose thread safety is in question here is not actually String, but char[]"

Not quite, it is String constructor that has the race condition, char array is incidental there.

Re: Breaking java.lang.String

#49

Is this actually a bug? The default assumption in Java is that types are not thread-safe unless otherwise specified. Attempting to use types in a way that exceeds their documented thread safety has always been allowed to leave your program in an inconsistent state.

I remember a bug in Visual/Digitalk Smalltalk: conversion from integer to string (method printString) was not thread safe, because it used global buffer for better performance. Very rarely - when we used a progress indicator in another thread (called Process in Visual Smalltalk) - this global buffer was overriden by another integer to string conversion... It took a year to find out the reason why our code sometimes got wrong results... I submitted the bug to Digitalk and they decided to not fix it (performance reasons). Only in later versions they abandoned the global buffer...

Re: Breaking java.lang.String

#50
That's a very interesting finding. Nowadays Java security is a joke, but back in the day, Java security was a serious topic. Users were able to run downloaded applets in their browser, so protecting the sandbox was important. It's very likely that using those kinds of "corrupted" strings would allow to break out of this sandbox, because that protection code definitely relied on strings being sane and correct.

I can't imagine this behaviour to cause much problem with modern Java, nobody runs untrusted code anyway. But good to know.

Post reply on HN